From: thor@arrakis.denver.co.us (Robert B. Hood)
Subject: cBASE - Part 1
Summary: A dBASE III+ Class in C++
Reply-To: thor@arrakis.denver.co.us
Organization: Bob's Programming Paradise, Lakewood, CO, USA
Date: Thu, 10 Sep 1992 13:41:51 GMT

I have received over 20 `me too's in two days, so I guess that
warrants a post.  Here is the C++ class to access and modify
dBASE III+ .DBF files.  It is still under construction (doesn't
do indexing or currently handle memo fields), but it is a start.

I will also include two test programs (sans databases) that actually
use the class.  This article contains CBASE.CPP, and subsequent
posts will contains the header file and test programs.

Enjoy!

------------------------- CBASE.CPP -----------------------------
// cBASE - A dBASE III+ class for accessing and manipulating .DBF files.
//                Copyright (C) 1992 Bob Hood
//
// Author      : Bob Hood (thor@arrakis.denver.co.us)
// Last Update : 08.01.92
// Header Files: cBASE.HPP
// Comments    : Class is still under construction, so use at your own risk.
//
//               You are free to copy and distribute this code as long as
//               you do not charge a fee for it, and the Copyright notice
//               above remains intact.

//  -- `To Do' list --
// 1. lock database header area when updating
// 2. deal with memo fields
// 3. implement a linked-list quick sort routine (for indexing)
// 4. create generic area locking routine by locking (record number +
//    1,000,000 or so).  This is how the big boys (Clipper, dBASE, etc.)
//    allow you to view a record even though another user has it locked!

#include    <stdio.h>
#include    <iostream.h>
#include    <conio.h>
#include    <stdlib.h>
#include    <fcntl.h>
#include    <io.h>
#include    <alloc.h>
#include    <string.h>
#include    <mem.h>
#include    <dos.h>

#include    "cbase.hpp"

#define DB_LOCK     0x00
#define DB_UNLOCK   0x01

//---------------------------------------------------------------------------
// constructor...
//---------------------------------------------------------------------------
dBASE::dBASE()
{
    data.db_name[0]    = '\0';
    data.database      = -1;

    data.field_rec          = NULL;
    data.record_hold        = NULL;
    data.index.index_chain  = NULL;
    data.relation_list      = NULL;
    data.field_data         = NULL;

    db_err                  = NO_PROBLEM;
    net_err                 = NO_PROBLEM;
}

//---------------------------------------------------------------------------
int dBASE::activate(char *db_name,char *alias)
{
    if(data.database != -1) return(ALREADY_OPEN_ERROR);

    data.database = open(db_name,O_RDWR|O_BINARY);

    if(data.database != -1)
    {
        strcpy(data.db_name,db_name);
        strcpy(data.alias,alias);

        eof_met         = FALSE;
        bof_met         = FALSE;
        update          = FALSE;
        file_locked     = FALSE;
        record_locked   = FALSE;
    }
    else
        return(DOS_OPEN_ERROR);

    return(getStructure());
}

//---------------------------------------------------------------------------
int dBASE::deactivate(void)
{
    INDEX_CHAIN     *hold1,*hold2;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    // flush any changed data contained in the record buffer...

    if(update)
    {
        if(record_locked || file_locked)
        {
            f2b();              // return the linked list to a stream
            writeRecord();      // and put the data back in the file
            if(record_locked) rUnLock();
        }
        else
            cout << "ERROR: Record #" << currRecord() << " not locked -- cannot update";
    }

    // ...and shut that puppy down!

    if(file_locked) fUnLock();

    close(data.database);

    if(data.field_rec != NULL)      free(data.field_rec);
    if(data.record_hold != NULL)    free(data.record_hold);
    if(data.index.index_chain != NULL) killIndex();
    if(data.relation_list != NULL)  free(data.relation_list);

    data.db_name[0]    = '\0';
    data.alias[0]      = '\0';
    data.database      = -1;
    data.field_rec     = NULL;
    data.record_hold   = NULL;
    data.relation_list = NULL;

    eof_met            = FALSE;
    bof_met            = FALSE;
    update             = FALSE;
    file_locked        = FALSE;
    record_locked      = FALSE;

    db_err             = NO_PROBLEM;

    return(db_err);
}

//---------------------------------------------------------------------------
int dBASE::getStructure(void)
{
    int         x,y;
    int         num_fields;
    int         more_fields;
    long        rec_pos;
    FIELD_REC   hold_field;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    lseek(data.database,0L,SEEK_END);   // jump to the end..
    dbSize = tell(data.database);       // see how long this file is...

    lseek(data.database,0L,SEEK_SET);   // rewind the file

    x = read(data.database,(char *)&data.dbf_head,sizeof(DBF_HEAD));

    if(x != sizeof(DBF_HEAD)) return(DOS_READ_ERROR);

    rec_pos = tell(data.database);      // position of the start of the field list
    data.field_list_offset = rec_pos;
    num_fields = 0;

    for(x = 0,y = 0;x < 2;x++)          // two passes
    {
        lseek(data.database,rec_pos,SEEK_SET);   // move to start of field list

        more_fields = TRUE;
        while(more_fields)
        {
            more_fields = (read(data.database,(char *)&hold_field,sizeof(FIELD_REC)) == sizeof(FIELD_REC));

            if(more_fields) more_fields = (hold_field.field_name[0] != 0x0D);
            if(more_fields)
            {
                if(x == 1)
                {
                    memmove((void *)&data.field_rec[y],(void *)&hold_field,
                            sizeof(FIELD_REC));
                    ++y;
                }
                else
                    ++num_fields;
            }
        }

        if(x == 0)      // first pass, create an array large enough to hold list
        {
            data.num_fields = num_fields;
            data.field_rec = (FIELD_REC *)calloc(num_fields,sizeof(FIELD_REC));
        }
    }

    // the record pointer should be positioned @ the first data record in this
    // database.  save it in the area structure

    data.data_records_offset = tell(data.database);
    data.current_record = 1L;

    data.record_hold = (void *)malloc(data.dbf_head.rec_size);

    // position the record pointer @ the first record, and read it into
    // the 'record_hold' area...

    gotoRecord(data.current_record);

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
int dBASE::gotoRecord(LONG rec_number,int lockIt)
{
    int         x,y;
    int         num_fields;
    int         more_fields;
    LONG        rec_pos;

    // see if there is a database currently open in this area

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    // do we have data sitting in the `data.record_hold' buffer?

    if(update)
    {
        if(!record_locked) return(RECORD_NOT_LOCKED_ERROR);

        f2b();              // return the linked list to a stream
        writeRecord();      // and put the data back in the file
    }

    x = NO_PROBLEM;

    if(record_locked && !file_locked) rUnLock();    // unlock the record

    // see if the requested record number is greater than those contained
    // in the database file...

    if(rec_number > data.dbf_head.last_rec) return(INVALID_RECORD_ERROR);

    // position the file pointer at the first record in the database

    lseek(data.database,(long)data.dbf_head.data_offset,SEEK_SET);

    // calculate the records offset from here...

    rec_pos = (rec_number - 1) * data.dbf_head.rec_size;

    // now position to that record

    lseek(data.database,rec_pos,SEEK_CUR);

    // and update the area record pointer

    data.current_record = rec_number;

    if(lockIt && !file_locked)
    {
        if(!rLock()) x = db_err;
    }

    // read the current record into 'record_hold' for this area

    read(data.database,data.record_hold,data.dbf_head.rec_size);

    // put us back to the start of this record in case we do a writeRecord()

    lseek(data.database,-data.dbf_head.rec_size,SEEK_CUR);

    // convert the stream of characters into a linked list of fields

    b2f();  // gets fields to linked list, and sets `update' to FALSE

    // make the new linked list publicly available

    field_list = data.field_data;

    return(x);
}

//---------------------------------------------------------------------------
int dBASE::goTop(void)
{
    return(gotoRecord(1L));
}

//---------------------------------------------------------------------------
int dBASE::goBottom(void)
{
    return(gotoRecord(data.dbf_head.last_rec));
}

//---------------------------------------------------------------------------
void *dBASE::getRecord(void)
{
    // see if there is a database currently open in this area

    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(NULL);
    }

    // return a pointer to the data currently being held in `record_hold'

    return((void *)data.record_hold);
}

//---------------------------------------------------------------------------
int dBASE::getRecordLen(void)
{
    // see if there is a database currently open in this area

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    // return the record len of the current database

    return(data.dbf_head.rec_size);
}

//---------------------------------------------------------------------------
int dBASE::writeRecord(void)
{
    // write the current record in `record_hold' (was converted with `f2b()'
    // in `gotoRecord()' above

    write(data.database,data.record_hold,data.dbf_head.rec_size);

    // put us back to the start of this record

    lseek(data.database,-data.dbf_head.rec_size,SEEK_CUR);

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
LONG dBASE::lastRecord(void)
{
    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(0L);
    }

    // return the record len of the current database

    return(data.dbf_head.last_rec);
}

//---------------------------------------------------------------------------
LONG dBASE::currRecord(void)
{
    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(0L);
    }

    // return the record len of the current database

    return(data.current_record);
}

//---------------------------------------------------------------------------
int dBASE::deleteRecord(void)
{
    char    *buff;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    if(!record_locked) return(RECORD_NOT_LOCKED_ERROR);

    buff = (char *)getRecord();
    *buff = '*';
    data.deleted = TRUE;

    update = TRUE;

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
int dBASE::recallRecord(void)
{
    char    *buff;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    buff = (char *)getRecord();
    *buff = ' ';
    data.deleted = FALSE;

    update = TRUE;

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
void dBASE::listInfo(void)
{
    int     x;

    // see if there is a database currently open in this area

    if(data.database == -1) return;

    printf("\n*** Structure of database \"%s\"\n\n",data.db_name);
    printf("Last update %2d/%2d/%2d\n",
            data.dbf_head.last_update[1],    // month
            data.dbf_head.last_update[2],    // day
            data.dbf_head.last_update[0]);   // year
    printf("Data offset %d\n",data.dbf_head.data_offset);
    printf("Record size %d\n",data.dbf_head.rec_size);

    printf("Number of records %lu\n\n",data.dbf_head.last_rec);
    printf("NAME        TYPE LEN DEC\n\n");

    x = 0;
    while(x < data.num_fields)
    {
        switch(data.field_rec[x].field_type)
        {
            case 'N':   printf("%-11s %-4c %3d %3d\n",
                                data.field_rec[x].field_name,
                                data.field_rec[x].field_type,
                                data.field_rec[x].len_info.num_size.len,
                                data.field_rec[x].len_info.num_size.dec);
                        break;

            default:    printf("%-11s %-4c %3d\n",
                                data.field_rec[x].field_name,
                                data.field_rec[x].field_type,
                                data.field_rec[x].len_info.char_len);
                        break;
        }

        ++x;
    }
}

//---------------------------------------------------------------------------
void dBASE::grab(char *from, char *to, char *dest)
{
    int     x;

    x = 0;
    while(from <= to)
    {
        dest[x] = *from;
        ++from;
        ++x;
    }
    dest[x] = '\0';
}

//---------------------------------------------------------------------------
char *dBASE::alias(void)
{
    // see if there is a database currently open in this area

    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(NULL);
    }

    // return the record len of the current database

    return(data.alias);
}

//---------------------------------------------------------------------------
int dBASE::newRecord(int lockIt)
{
    int         x,y;
    long        rec_pos;
    char        *holdfield;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    lseek(data.database,0L,SEEK_END);   // go to bottom of file

    holdfield = (char *)malloc(data.dbf_head.rec_size);

    memset(holdfield,' ',data.dbf_head.rec_size);

    x = write(data.database,holdfield,data.dbf_head.rec_size);
    free(holdfield);

    if(x != data.dbf_head.rec_size) return(DOS_WRITE_ERROR);

    dbSize += x;                // update the size pointer...

    // dynamically update the database header

    lseek(data.database,0L,SEEK_SET);   // rewind the file

    x = read(data.database,(char *)&data.dbf_head,sizeof(DBF_HEAD));
    if(x != sizeof(DBF_HEAD)) return(DOS_READ_ERROR);

    ++data.dbf_head.last_rec;   // increment the number of records by one

    // update the `last_update' field <<-- needs to be implemented

    lseek(data.database,0L,SEEK_SET);   // rewind the file
    if(lock(data.database,0L,sizeof(DBF_HEAD),DB_LOCK))
    {
        write(data.database,(char *)&data.dbf_head,sizeof(DBF_HEAD));
        lock(data.database,0L,sizeof(DBF_HEAD),DB_UNLOCK);
    }
    else
        return(RECORD_LOCK_FAILED_ERROR);

    // is there a `file_locked' condition?

    if(file_locked)
    {
        // yes, expand the current lock to include the new record

        dbSize += data.dbf_head.rec_size;

        if(!lock(data.database,0L,dbSize,DB_LOCK))
            return(FILE_LOCK_FAILED_ERROR);

        lockIt = FALSE;     // record is already implicitly locked,
                            // so don't be redundant
    }

    gotoRecord(data.dbf_head.last_rec);     // re-position to the new record

    x = NO_PROBLEM;

    if(lockIt && !rLock()) x = db_err;

    return(x);
}

//---------------------------------------------------------------------------
int dBASE::b2f(void)
{
    int         x,y,z;
    int         left,right;
    int         year,month,day;
    char        logical;
    char        text[256];
    char        format[25];
    char        *buff;
    char        *hold;
    float       number;
    long        long_number;
    FIELD_DATA  *new_field;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    buff = (char *)getRecord();
    y = getRecordLen();

    data.deleted = (*buff == '*') ? TRUE : FALSE;

    ++buff;

    clearDBFields();        // release the linked list of field data

    x = 0;
    while(x < data.num_fields)
    {
        switch(data.field_rec[x].field_type)
        {
            case 'D':   grab(buff,buff+3,text);
                        year = atoi(text);

                        grab(buff+4,buff+5,text);
                        month = atoi(text);

                        grab(buff+6,buff+7,text);
                        day = atoi(text);

                        buff += 8;

                        if((new_field = newDBField()) == NULL)
                        {
                            ++x;
                            continue;
                        }

                        new_field->field_type = 'D';
                        new_field->data.date.month = month;
                        new_field->data.date.day   = day;
                        new_field->data.date.year  = year;

                        break;

            case 'C':   y = data.field_rec[x].len_info.char_len - 1;
                        grab(buff,buff+y,text);

                        buff += y;
                        ++buff;

                        if((new_field = newDBField()) == NULL)
                        {
                            ++x;
                            continue;
                        }

                        new_field->field_type = 'C';
                        strcpy(new_field->data.text,text);

                        break;

            case 'L':   logical = *buff;
                        ++buff;

                        if((new_field = newDBField()) == NULL)
                        {
                            ++x;
                            continue;
                        }

                        new_field->field_type = 'L';
                        new_field->data.boolean = logical;

                        break;

            case 'M':   grab(buff,buff+9,text);
                        buff += 10;
                        break;

            case 'N':   y = data.field_rec[x].len_info.num_size.len - 1;
                        grab(buff,buff+y,text);

                        buff += y;
                        ++buff;

                        z = data.field_rec[x].len_info.num_size.dec;

                        if((new_field = newDBField()) == NULL)
                        {
                            ++x;
                            continue;
                        }

                        if(z > 0)
                        {
                            sprintf(format,"%%%3d.%03df",y+1,z);
                            sscanf(text,"%f",&number);

                            new_field->field_type = 'F';
                            new_field->data.float_number.number = number;
                            strcpy(new_field->data.float_number.format,format);
                        }
                        else
                        {
                            long_number = atol(text);

                            new_field->field_type = 'N';
                            new_field->data.long_number = long_number;
                        }
                        break;
        }

        ++x;
    }

    update = FALSE;

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
FIELD_DATA *dBASE::newDBField(void)
{
    FIELD_DATA  *this_field;
    FIELD_DATA  *new_field;

    new_field = (FIELD_DATA *) malloc(sizeof(FIELD_DATA));
    if(new_field == NULL) return(NULL);

    if(data.field_data == NULL)
        data.field_data = new_field;
    else
    {
        this_field = data.field_data;
        while(this_field->next != NULL) this_field = this_field->next;
        this_field->next = new_field;
    }

    new_field->next = NULL;
    return(new_field);
}

//---------------------------------------------------------------------------
void dBASE::clearDBFields(void)
{
    FIELD_DATA  *this_field;
    FIELD_DATA  *that_field;

    // clear out an previous data
    if(data.field_data != NULL)
    {
        this_field = data.field_data;
        while(this_field != NULL)
        {
            that_field = this_field->next;
            free(this_field);
            this_field = that_field;
        }
        data.field_data = NULL;
    }
}

//---------------------------------------------------------------------------
int dBASE::f2b(void)
{
    int         x,y,z;
    int         left,right;
    int         year,month,day;
    int         logical;
    char        text[256];
    char        *buff;
    char        *hold;
    float       number;
    long        long_number;
    FIELD_DATA  *this_field;

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    buff = (char *)getRecord();
    ++buff;                 // skip over the "deleted" marker

    y = getRecordLen();

    this_field = data.field_data;

    while(this_field != NULL)
    {
        switch(this_field->field_type)
        {
            case 'D':   *text = 0;
                        sprintf(hold,"%04d",this_field->data.date.year);
                        strcat(text,hold);
                        sprintf(hold,"%02d",this_field->data.date.month);
                        strcat(text,hold);
                        sprintf(hold,"%02d",this_field->data.date.day);
                        strcat(text,hold);

                        hold = text;
                        while(*hold != 0) *buff++ = *hold++;

                        break;

            case 'C':   hold = this_field->data.text;
                        while(*hold != 0) *buff++ = *hold++;
                        break;

            case 'L':   *buff = this_field->data.boolean;
                        ++buff;
                        break;

            case 'M':
                        break;

            case 'F':   sprintf(text,this_field->data.float_number.format,
                                     this_field->data.float_number.number);
                        hold = text;
                        while(*hold != 0) *buff++ = *hold++;
                        break;

            case 'N':   sprintf(text,"%ld",this_field->data.long_number);
                        hold = text;
                        while(*hold != 0) *buff++ = *hold++;
                        break;
        }

        this_field = this_field->next;
    }

    buff = (char *)getRecord();
    ++buff;
    putchar('(');
    for(z = 0;z < y;z++) putchar(*buff++);
    putchar(')');
    getch();

    return(NO_PROBLEM);
}

//---------------------------------------------------------------------------
void dBASE::printDBFields(void)
{
    FIELD_DATA  *this_field;

    if(data.database == -1) return;

    this_field = data.field_data;

    while(this_field != NULL)
    {
        switch(this_field->field_type)
        {
            case 'D':   printf("\t%02d/%02d/%04d\n",
                                this_field->data.date.month,
                                this_field->data.date.day,
                                this_field->data.date.year);
                        break;

            case 'C':   printf("\t%s\n",this_field->data.text);
                        break;

            case 'L':   printf("\t%c\n",this_field->data.boolean);
                        break;

            case 'M':
                        break;

            case 'F':   printf("\t");
                        printf(this_field->data.float_number.format,
                               this_field->data.float_number.number);
                        printf("\n");
                        break;

            case 'N':   printf("\t%ld\n",this_field->data.long_number);
                        break;
        }

        this_field = this_field->next;
    }
}

//---------------------------------------------------------------------------
void dBASE::skip(LONG num)
{
    LONG    hold;

    if(data.database == -1) return;

    eof_met = FALSE;
    bof_met = FALSE;

    if(data.index.index_chain != NULL)  // we have an index active
    {
        if(num < 0)     // we're skipping backward
        {
            hold = num;
            while(hold)
            {
                if(data.index.current_record->prev == NULL)     // @ BOF
                {
                    hold = data.index.current_record->record;
                    bof_met = TRUE;
                    break;
                }

                --hold;
                data.index.current_record = data.index.current_record->prev;
            }
            if(!bof_met) hold = data.index.current_record->record;
        }
        else            // we're skipping forward
        {
            hold = num;
            while(hold)
            {
                if(data.index.current_record->next == NULL)     // @ EOF
                {
                    hold = data.index.current_record->record;
                    eof_met = TRUE;
                    break;
                }

                --hold;
                data.index.current_record = data.index.current_record->next;
            }
            if(!eof_met) hold = data.index.current_record->record;
        }
    }
    else
    {
        hold = data.current_record + num;

        if(hold > data.dbf_head.last_rec)
        {
            eof_met = TRUE;
            hold = data.dbf_head.last_rec;
        }
        else if(hold < 1)
        {
            bof_met = TRUE;
            hold = 1;
        }
    }

    data.current_record = hold;
    gotoRecord(data.current_record);
}

//---------------------------------------------------------------------------
int dBASE::eof(void)
{
    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    return(eof_met);
}

//---------------------------------------------------------------------------
int dBASE::bof(void)
{
    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    return(bof_met);
}

//---------------------------------------------------------------------------
// this function is a front-end to indexOn(int) which locates the field
// indicated by its name, and then forwards that field's # to the real
// routine.
//---------------------------------------------------------------------------
int dBASE::indexOn(char *fieldName)
{
    int     x;

    // see if there is a database currently open in this area

    if(data.database == -1) return(NO_DB_OPEN_ERROR);

    x = 0;
    while(x < data.num_fields)
    {
        if(stricmp(data.field_rec[x].field_name,fieldName) == 0) break;
        ++x;
    }

    // see if we located it...
    if(x == data.num_fields) return(INVALID_FIELD_NAME_ERROR);

    return(indexOn(x + 1));     // convert x to 1-based indexing
}

//---------------------------------------------------------------------------
int dBASE::indexOn(int fieldNumber)     // fieldNumber is 1-based (1234...)
{
    INDEX_CHAIN *this_link,*new_link;
    FIELD_DATA  *this_field;
    FIELD_REC   *this_rec;
    int     x,first;

    // see if there is a database currently open in this area

    if(data.database == -1) return(NO_DB_OPEN_ERROR);
    if(fieldNumber < 1 || fieldNumber > data.num_fields)
        return(INVALID_FIELD_NUM_ERROR);

    // is there an index currently active?

    if(data.index.index_chain != NULL) killIndex();     // keel it... 8^)

    // build a linked list of FIELD DATA/RECORD NUMBER pairs that will
    // act as our index.

    data.index.index_field = x - 1;

    gotoRecord(1L);

    while(!eof())
    {
        new_link = (INDEX_CHAIN *)malloc(sizeof(INDEX_CHAIN));

        if(data.index.index_chain == NULL)
            data.index.index_chain = new_link;
        else
        {
            this_link->next = new_link;
            new_link->prev = this_link;
        }

        this_link = new_link;
        this_link->next = NULL;

        // locate the field data
        x = 0;
        this_field = data.field_data;
        while(x < fieldNumber)
        {
            this_field = this_field->next;
            ++x;
        }

        switch(this_field->field_type)
        {
            case 'D':   sprintf(this_link->key.textKey,"%02d/%02d/%04d",
                                this_field->data.date.month,
                                this_field->data.date.day,
                                this_field->data.date.year);
                        break;

            case 'C':   strcpy(this_link->key.textKey,this_field->data.text);
                        break;

            case 'L':   this_link->key.logicalKey = this_field->data.boolean == 'F' ? FALSE : TRUE;
                        break;

            case 'M':
                        break;

            case 'F':   this_link->key.floatKey = this_field->data.float_number.number;
                        break;

            case 'N':   this_link->key.longKey = this_field->data.long_number;
                        break;
        }
        this_link->record = currRecord();

        skip();
    }

    // since the other routines check for an active index, all we have left
    // to do is sort it...
}

//---------------------------------------------------------------------------
void dBASE::killIndex(void)
{
    INDEX_CHAIN *this_link;
    INDEX_CHAIN *that_link;

    // clear out any previous index

    if(data.index.index_chain != NULL)
    {
        this_link = data.index.index_chain;
        while(this_link != NULL)
        {
            that_link = this_link->next;
            free(this_link);
            this_link = that_link;
        }

        data.index.index_chain    = NULL;
        data.index.current_record = NULL;
    }
}

//---------------------------------------------------------------------------
int dBASE::fLock(void)
{
    // ask the network to lock this file...

    file_locked = lock(data.database,0L,dbSize,DB_LOCK);

    if(!file_locked)
        db_err = FILE_LOCK_FAILED_ERROR;
    else
        record_locked = TRUE;   // locked file means locked records

    return(file_locked);
}

//---------------------------------------------------------------------------
int dBASE::fUnLock(void)
{
    // ask the network to unlock this file...

    file_locked = !lock(data.database,0L,dbSize,DB_UNLOCK);

    if(file_locked)
        db_err = FILE_UNLOCK_FAILED_ERROR;
    else
        record_locked = FALSE;   // unlocked file means unlocked records

    return(!file_locked);
}

//---------------------------------------------------------------------------
int dBASE::rLock(void)
{
    long        pos;

    // ask the network to lock this record...

    // see ::lock() for an explanation of this mechanism.

    pos = tell(data.database);
    record_locked = lock(data.database,pos,data.dbf_head.rec_size,DB_LOCK);

    if(!record_locked) db_err = RECORD_LOCK_FAILED_ERROR;

    return(record_locked);
}

//---------------------------------------------------------------------------
int dBASE::rUnLock(void)
{
    long        pos;

    // ask the network to unlock this record...

    pos = tell(data.database);
    record_locked = !lock(data.database,pos,data.dbf_head.rec_size,DB_UNLOCK);

    if(record_locked) db_err = RECORD_UNLOCK_FAILED_ERROR;

    return(!record_locked);
}

//---------------------------------------------------------------------------
int dBASE::lock(int file,long offset,long length,int function)
{
    union       REGS xr;
    int         result;

    net_err = -1;

    // if `length' is 0L, what the hell are they trying to lock?!

    if(length == 0L) return(FALSE);

    // we use the DOS 21h/5Ch function (Lock/Unlock File Access) to
    // allow locking compatibility with networks that support this interrupt
    // (such as Novell NetWare, LANtastic, and Banyan Vines).

    // AL = [0 to lock/1 to unlock]
    // BX = File Handle
    // CX = High word of offset
    // DX = Low word of offset
    // SI = High word of length
    // DI = Low word of length
    // If CF = 1, AX = error code

    xr.h.ah = 0x5C;
    xr.h.al = function;     // lock/unlock request
    xr.x.bx = file;         // file handle

    // starting offset in `file'

    if(offset != 0L)
    {
        xr.x.cx = (INT)((offset & 0x7FFF0000) >> 8);
        xr.x.dx = (INT)(offset & 0x0000FFFF);
    }
    else    // skip the contortions for a 0L value
    {
        xr.x.cx = 0x0000;
        xr.x.dx = 0x0000;
    }

    // number of bytes in `file' to lock

    xr.x.si = (INT)((length & 0x7FFF0000) >> 8);
    xr.x.di = (INT)(length & 0x0000FFFF);

    int86(0x21,&xr,&xr);

    // we will receive an error code of 0x0001 (Invalid Function) if file/record
    // locking is not appropriate or possible for the current medium (such as
    // a file residing on a local hard drive without SHARE loaded).  In such
    // an instance, simply act as though the file/record is locked.

    if(xr.x.cflag && xr.x.ax != 0x0001)
    {
        net_err = xr.x.ax;
        return(FALSE);
    }
    else
        return(TRUE);
}

//---------------------------------------------------------------------------
int dBASE::getDBErr(void)
{
    int     hold;

    hold = db_err;
    db_err = NO_PROBLEM;        // reset the error holder on each call
    return(hold);
}

//---------------------------------------------------------------------------
int dBASE::getNetErr(void)
{
    int     hold;

    hold = net_err;
    net_err = NO_PROBLEM;       // reset the error holder on each call
    return(hold);
}

//---------------------------------------------------------------------------
// this function is a front-end to getField(int) which locates the field
// indicated by its name, and then forwards that field's # to the real
// routine.
//---------------------------------------------------------------------------
FIELD_DATA *dBASE::getField(char *fieldName)
{
    int     x;

    // see if there is a database currently open in this area

    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(NULL);
    }

    x = 0;
    while(x < data.num_fields)
    {
        if(stricmp(data.field_rec[x].field_name,fieldName) == 0) break;
        ++x;
    }

    // see if we located it...
    if(x == data.num_fields)
    {
        db_err = INVALID_FIELD_NAME_ERROR;
        return(NULL);
    }

    return(getField(x + 1));     // convert x to 1-based indexing
}

//---------------------------------------------------------------------------
FIELD_DATA *dBASE::getField(int fieldNumber)     // fieldNumber is 1-based (1234...)
{
    FIELD_DATA  *this_field;
    int     x,first;

    // see if there is a database currently open in this area

    if(data.database == -1)
    {
        db_err = NO_DB_OPEN_ERROR;
        return(NULL);
    }

    if(fieldNumber < 1 || fieldNumber > data.num_fields)
    {
        db_err = INVALID_FIELD_NUM_ERROR;
        return(NULL);
    }

    this_field = data.field_data;
    x = fieldNumber - 1;

    while(this_field != NULL && x)
    {
        this_field = this_field->next;
        --x;
    }

    if(this_field == NULL)      // shouldn't occur, but here for robustness
    {
        db_err = INVALID_FIELD_NUM_ERROR;
        return(NULL);
    }

    return(this_field);
}

---------------------------- end CBASE.CPP ----------------------------


-- 
Bob Hood    thor@arrakis.denver.co.us     H: 303-980-8392  W: 303-632-2180
---------------------------------------------------------------------------
When people are free to do as they please, they usually imitate each other.