Jim Kyle. Btrieve complete.
A Guide for Developers and System Administrators.
Addison-Wesley Publi****ng Company. 1995.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* DUMPDATA - Jim Kyle - February 1995 *
* *
* Lists all data records from any version of Btrieve file *
* without requiring Btrieve engine to be installed in *
* system. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <conio.h>
#ifdef __TURBOC__
#include <alloc.h> // Borland-specific
#else
#include <malloc.h> // Microsoft version
#endif
#include <string.h>
#include <ctype.h>
#include "btrieve.h"
FILE *fp; // global for convenience
FCRTOP Fcr;
int PageSize, RecLen, PhyLen;
char *PgmName =3D "DUMPDATA";
char outbuf[80];
FILE *fpout =3D stdout;
int rectype =3D 0; // 0 fixed, 1 variable,
// 2 var trunc, 3 compressed, 4 uses VAT
VRECPTR Vrec; // current vrec pointer
int fragno; // fragment number from Vrec
long fragpg; // vrec logical page number from Vrec
long fragfo; // file offset to physical page
int *fragpp; // fragment index array base
int fragi, // index into fragpp array
frago, // offset to start of fragment
fragl; // length of fragment
BYTE Vbfr[MAXBTRPG];
char wrkbuf[MAXBTRPG]; // for reading anything into
char *fmt[] =3D {
"original",
"new"
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure undoes Btrieve's word-swapping. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static long swap( long n )
{ asm les dx,n; // ASM trick for simplicity
asm mov ax,es;
// return ((n>>16)&0xFFFF) | (n<<16);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure dumps data, translating to ASCII *
* if in printable range, else outputting as hex. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void dumpdata( BYTE *data, int nbr )
{ int i, j;
char *h =3D "0123456789ABCDEF";
for(i=3D0; i<nbr; i+=3D64)
{ for( j=3D0; j<64; j++ )
if( (i+j) < nbr )
putchar( h[ (data[i+j]>>4) & 15] );
printf( "\n\t " );
for( j=3D0; j<64; j++ )
if( (i+j) < nbr )
putchar( h[ data[i+j] & 15] );
printf( "\n\t " );
for( j=3D0; j<64; j++ )
if( (i+j) < nbr )
putchar( isprint( data[i+j] ) ? data[i+j] : '*' );
printf( "\n\t " );
for( j=3D0; j<64; j++ )
if( (i+j) < nbr )
putchar( '-' );
if( j =3D=3D 64 )
printf( "\n\t " );
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure converts a Logical Page number to *
* a file offset, using the PAT for new format files. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static long lp_pp( long lp )
{ long ret, pat1, pat2;
unsigned short u1, u2, pppat;
if( CurFmt !=3D 1 ) // only do lookup for new
{ ret =3D 2; // first PAT pair on 2, 3
pppat =3D (PageSize >> 2) - 2; // pages per PAT
while( lp > pppat ) // off current page
{ lp -=3D pppat; // so tally down index
ret +=3D (PageSize >> 2); // up the PAT pp nbr
}
pat1 =3D ret * (long)PageSize; // first PAT of pair
pat2 =3D pat1 + (long)PageSize; // second right after it
fseek( fp, pat1+4L, 0 ); // get both usage counts
fread( &u1, 2, 1, fp );
fseek( fp, pat2+4L, 0 );
fread( &u2, 2, 1, fp );
if( u1 > u2 ) // choose most recent one
ret =3D pat1;
else
ret =3D pat2;
ret +=3D (long)(( lp << 2 ) + 4L ); // position in PAT
fseek( fp, ret, 0 );
fread( &lp, 4, 1, fp ); // read it into LP
lp =3D swap( lp ); // and un-word-swap it
lp &=3D 0xFFFFFFL;
}
if( lp =3D=3D 0xFFFFFFL || lp =3D=3D -1L ) // NULL pointer values
ret =3D -1L;
else // convert to offset
ret =3D lp * PageSize;
return ret;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure writes a message to CRT and then *
* waits for user to press ENTER key. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void oflomsg( char * p, int r, long cpos )
{ printf( "\07%s buffer overflow!\07\n"
"Press ENTER to continue\n%d\t ", p, r );
while( getch() !=3D 13 )
/* wait for user */ ;
dumpdata( (BYTE *)wrkbuf, r );
fseek( fp, cpos + (long)PhyLen, 0 );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure checks one record for validity and *
* then outputs its data, expanding as necessary. *
* Flag byte added as promised in text. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void DmpRec( void ) // dumps single record
{ long cpos =3D ftell( fp ); // save position at entry
int i, b, r =3D RecLen, count, state;
if( rectype !=3D 3 && CurFmt =3D=3D 2 ) // new format usage test
{ fread( &i, 2, 1, fp );
if( !i ) // ignore unused records
{ fseek( fp, cpos + (long)PhyLen, 0 );
return;
}
fread( wrkbuf, PhyLen-2, 1, fp ); // load workbuf with data
}
else // test for empty record
{ int empty =3D 1;
fread( wrkbuf, PhyLen, 1, fp ); // load wrkbuf with data
for( i=3D4; i<PhyLen; i++ ) // skip over pointer
if( wrkbuf[i] ) // something there
{ empty =3D 0; // so not empty
break;
}
if( empty ) // restore file, ignore
{ fseek( fp, cpos + (long)PhyLen, 0 );
return;
}
}
if( rectype ) // var, trunc, compr
{ long fps =3D ftell( fp ); // save file position
int lofs;
BYTE cmpalg, savect, countflag;
switch( rectype )
{
case 1: // VARIABLE LENGTH DATA
case 2: // BLANK TRUNCATION
memcpy( &Vrec, wrkbuf + r, 4 );
memcpy( &count, wrkbuf + r + 4, 2 );
fragpp =3D (int *)Vbfr; // set pointer
nxvr: fragno =3D VRFrag( Vrec ); // for multiple frags
fragpg =3D VRPage( Vrec );
fragfo =3D lp_pp( fragpg );
if( fragfo =3D=3D -1L || fragno > 254 )
goto vrdun; // no more to do
fseek( fp, fragfo, 0 ); // read page into buffer
fread( &Vbfr, MAXBTRPG, 1, fp );
fragi =3D ((PageSize - 1) >> 1 ) - fragno;
frago =3D fragpp[ fragi ] & 0x7FFF;
for(lofs=3D1; fragpp[fragi - lofs] =3D=3D -1; lofs++)
/* all done in test! */ ;
fragl =3D (fragpp[ fragi - lofs ] & 0x7FFF ) - frago;
if( CurFmt =3D=3D 2 || fragpp[fragi] & 0x8000 )
{ Vrec =3D *(VRECPTR *)(&Vbfr[ frago ]);
frago +=3D sizeof( VRECPTR );
fragl -=3D sizeof( VRECPTR );
}
else
{ Vrec.lo =3D Vrec.mid =3D Vrec.hi =3D Vrec.frag =3D 0x00FF;
}
memcpy( wrkbuf + r, Vbfr + frago, fragl );
r +=3D fragl;
goto nxvr; // check for next frag
vrdun: if( rectype =3D=3D 2 ) // restore blanks
{ while( count-- && r < 4095 ) // stay in buffer!
wrkbuf[ r++ ] =3D ' ';
}
break;
case 3: // COMPRESSED DATA
case 5: // COMPRESSED, VARIABLE
memcpy( &Vrec, wrkbuf + r, 4 );
fragpp =3D (int *)Vbfr; // set pointer
cmpalg =3D wrkbuf[4];
if( !cmpalg ) // deleted record, ignore
{ fseek( fp, cpos + (long)PhyLen, 0 );
return;
}
count =3D 0; // clear count vars
savect =3D 0;
countflag =3D 0;
r =3D 0; // expansion index
state =3D 1; // copy strings first
nxcr: fragno =3D VRFrag( Vrec ); // for multiple frags
fragpg =3D VRPage( Vrec );
fragfo =3D lp_pp( fragpg ); // actual file offset
if( fragfo =3D=3D -1L || fragno > 254 )
goto crdun; // end of chain
fseek( fp, fragfo, 0 ); // read page into buffer
fread( &Vbfr, PageSize, 1, fp );
fragi =3D ((PageSize - 1) >> 1 ) - fragno;
frago =3D fragpp[ fragi ] & 0x7FFF;
fragl =3D (fragpp[ fragi - 1 ] & 0x7FFF ) - frago;
if( CurFmt =3D=3D 2 || fragpp[fragi] & 0x8000 )
{ Vrec =3D *(VRECPTR *)(&Vbfr[ frago ]);
frago +=3D sizeof( VRECPTR );
fragl -=3D sizeof( VRECPTR );
}
else
{ Vrec.lo =3D Vrec.mid =3D Vrec.hi =3D Vrec.frag =3D 0x00FF;
}
if( countflag ) // count spans frags
{ frago--; // adj offset, length
fragl++;
Vbfr[frago] =3D savect; // set first byte in
count =3D 0; // clear out count
savect =3D 0; // clear save byte
countflag =3D 0; // clear flag
}
for( i=3D0; i<fragl; ) // decompression loop
{ if( count < 1 ) // get new count
{ count =3D *(int *)(Vbfr + frago + i );
i +=3D 2; // advance pointer
}
if( i >=3D fragl ) // at fragment end
break; // so get another
if( state ) // process data pair
{ while( count-- ) // copy is state 1
{ wrkbuf[ r++ ] =3D Vbfr[ frago + (i++) ];
if( r > 4090 ) // error trap...
{ oflomsg( "Copy", r, cpos );
return; // skip this record
}
if( i =3D=3D fragl && // string spans frags
count ) // and isn't done yet
break; // get next fragment
} // copy loop
if( count < 1 )
state =3D 0; // repeat next pair
}
else
{ while( count-- ) // repeat is state 0
{ wrkbuf[ r++ ] =3D Vbfr[ frago + i ];
if( r > 4090 ) // error trap...
{ oflomsg( "Repeat", r, cpos );
return; // skip this record
}
} // repeat loop
i++; // over repeat byte
state =3D 1; // copy next pair
}
if( i =3D=3D fragl-1 ) // count spans frags
{ savect =3D Vbfr[ frago + i++ ];
countflag =3D 1; // flag byte as saved
break; // get next
}
} // decompression loop
goto nxcr; // to get next fragment
crdun: break; // record complete now
}
fseek( fp, fps, 0 );
}
Fcr.Nrecs--; // tally down count
if( fpout =3D=3D stdout )
{ printf( "%d,\t ", r ); // human-readable format
dumpdata( (BYTE *)wrkbuf, r );
putchar( '\n' );
}
else
{ fprintf( fpout, "%d,", r ); // BUTIL -SAVE format
for( i=3D0; i<r; i++ )
{ b =3D 255 & wrkbuf[i];
fprintf( fpout, "%c", b );
}
fprintf( fpout, "\r\n" );
}
fseek( fp, cpos + (long)PhyLen, 0 ); // restore file position
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure goes through all possible records on *
* a page and calls DmpRec for each. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void DumpRecs( void )
{ int recpg =3D (PageSize - 6) / PhyLen; // page capacity
int currec; // current record
for( currec=3D0; currec < recpg; currec++ )
DmpRec();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure cycles through all pages, calling *
* DumpRecs routine for each data page in turn. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void DoItToIt( void )
{ long x, fps =3D 0L, pgct;
unsigned u;
int lpg=3D1;
char *rt[] =3D { "Fixed Length", "Variable Length",
"Variable, truncated", "Compressed",
"Uses VAT, not sup****ted",
"Compressed variable-length" };
RecLen =3D Fcr.RecLen; // save global values
PhyLen =3D Fcr.PhyLen;
pgct =3D swap( Fcr.Npages ); // get number of pages
Fcr.Nrecs =3D swap( Fcr.Nrecs );
printf( "File %s is in %s format; pagesize =3D %d, "
"has %ld pages\n",
outbuf,
fmt[CurFmt-1],
PageSize,
pgct );
printf( "Record count =3D %ld (%s).\n\n",
Fcr.Nrecs, rt[rectype] );
if( Fcr.Nrecs < 1L || rectype =3D=3D 4 ) // VAT's not sup****ted
return;
printf( "Write SAVE file, or VIEW data on CRT (S or V )? " );
for( u=3D1; u; )
switch( getch() )
{
case 's':
case 'S':
printf( "\nSave to filename: " );
gets( wrkbuf ); // get filename
for( u=3D0; wrkbuf[u] > 0x1F; u++ ) // find end of name
;
wrkbuf[u] =3D 0;
if( strlen(wrkbuf) ) // no name means view
fpout =3D fopen( wrkbuf, "wb" );
case 'v': // fall through
case 'V':
u =3D 0;
putchar( '\n' );
break;
case 27:
case 3:
return;
}
if( CurFmt > 1 )
lpg =3D 1; // new starts at one
else
lpg =3D 0; // old starts at zero
for( ; lpg < (unsigned)pgct; lpg++ ) // do rest of pages
{ fps =3D lp_pp( lpg ); // convert to file offset
if( fps < 0L )
break; // NULL-pointer, get out
fseek( fp, fps, 0 ); // seek to start of page
fread( &x, 4, 1, fp ); // read header & usage
fread( &u, 2, 1, fp );
if( (u & 0x8000) ) // dump data records
DumpRecs();
}
if( fpout !=3D stdout ) // close data file
{ fprintf( fpout, "%c", 0x1A ); // after adding EOF mark
fclose( fpout );
fpout =3D stdout;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure determines a file's type, then *
* loads Fcr, and establishes record type. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int GetFormat( void )
{ int fmt =3D 0; // 0 not Btrieve
// 1 old, 2 new
int testbuf[5]; // test first 10 bytes
fread( testbuf, 5, 2, fp );
if( testbuf[1] =3D=3D 0 ) // page sequence must be zero
{ if( testbuf[0] =3D=3D 0 )
{ fmt =3D 1; // original format
fseek( fp, 0L, 0 );
}
else if( testbuf[0] =3D=3D 0x4346 ) // 'FC' signature
{ fmt =3D 2;
fseek( fp, (long)testbuf[4]+4, 0 ); // check next FCR
fread( &testbuf[0], 2, 1, fp );
if( testbuf[0] > testbuf[2] ) // second is valid
fseek( fp, (long)testbuf[4], 0 );
else // use the first
fseek( fp, 0L, 0 );
}
}
if( fmt ) // load valid FCR
{ fread( &Fcr, 1, sizeof( FCRTOP ), fp );
PageSize =3D Fcr.PagSize;
if( Fcr.UsrFlgs & 8 &&
( Fcr.VRecsOkay || Fcr.UsrFlgs & 1 ))
rectype =3D 5; // compressed variable data
else if( fmt =3D=3D 2 && Fcr.UsrFlgs & 0x0800 )
rectype =3D 4; // uses VAT's
else if( Fcr.UsrFlgs & 8 )
rectype =3D 3; // compressed fixed data
else if( Fcr.VRecsOkay || Fcr.UsrFlgs & 1 )
{ if( (BYTE)Fcr.VRecsOkay =3D=3D 0x00FD || Fcr.UsrFlgs & 2 )
rectype =3D 2; // var trunc
else
rectype =3D 1; // variable length
}
else
rectype =3D 0; // fixed length
}
else
PageSize =3D 0;
return fmt;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure processes a single file. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int Do_File( char * fnm )
{ int ret =3D 0; // assume success
strupr( fnm );
fp =3D fopen( fnm, "rb" );
if( fp )
{ strcpy( outbuf, fnm ); // save for re****ts
CurFmt =3D GetFormat();
switch( CurFmt )
{
case 0:
printf( "%s is not a Btrieve file.\n", fnm );
ret =3D 1;
break;
case 1:
case 2:
DoItToIt();
break;
default:
puts( "Undefined format code, should never happen!" );
ret =3D 99;
break;
}
fclose( fp );
}
else
{ perror( fnm );
ret =3D 2;
}
return ret;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure tells how to use the program. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Usage( void )
{ printf( "Usage: %s file1 [file2 [...]]\n", PgmName );
printf( "\t where filenames can continue until command line "
"is full\n" );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure displays a standard banner heading *
* each time the program runs. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Banner( void )
{ printf( "\n\t %s - from \"Btrieve Complete\" by Jim Kyle\n",
PgmName );
printf( "\t Copyright 1995 by Jim Kyle - All Rights"
" Reserved\n" );
printf( "\t Use freely, but do not distribute "
"commercially.\n\n" );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* *
* This procedure is program entry point. *
* *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int main( int argc, char **argv )
{ int retval =3D 0;
Banner();
if( argc < 2 )
{ Usage();
retval =3D 255;
}
else
{ int i;
for( i=3D1; i < argc; i++ )
{ retval =3D Do_File( argv[i] ); // process each file
if( retval ) // if error, wait
while( getch() !=3D 13 )
/* wait right here for CR */ ;
}
}
return retval;
}
daniel.y...@[EMAIL PROTECTED]
ra=F0=EB:
> Hello,
>
> I am writing an application in C# that I am trying to integrate with a
> legacy program that stores its information in Btrieve databases. I am
> looking for information on how to do this.
>
> Due to financial constraints, as well as licensing problems,
> purchasing a copy of Progressive Server is not very feasible.
> Basically, my program needs to be able to read the information stored
> in this database without installing Progressive.
>
> I am basically looking for one of two things: an open-source library
> for reading btrieve data files, or a description of the file format.
>
> I have been able to find many descriptions of file.ddf and field.ddf,
> but the program I am integrating with only seems to use an index.ddf.
> Any of the freeware programs I have found require a file.ddf and
> field.ddf file to work.
>
> There have been some hints that the SQL reference manual from the
> Progressive website might have the file format I'm looking for, but I
> have not been able to find any details.
>
> This is all further complicated by the fact that I have no idea what
> version of the file format I am working with, but I suspect it to be
> pre-6.15.
>
> If anyone could point me towards some solutions to any of these
> problems, I would really appreciate it. I have been banging my head
> against a wall all week. Thanks.
>
> -Daniel


|