TIP: Click on subject to list as thread! ANSI
echo: clipper
to: PAUL LAUFER
from: PIERRE FOREST
date: 1996-12-06 08:41:00
subject: dBase3 tools

Le 11-30-96 13:47, Paul Laufer ‚crivait … All:
PL> I do a lot of database file damage recovery and am wondering
PL> if anyone out there has knowledge of software tools to help.
PL> It's for dBase3 .DBF files, DBT's and NTX's.  Some of the
PL> tools I currently use are dSalvage, DBTcheck, DBU and FoxPlus.
PL> I'm willing to spend the money for something that can expand
PL> my abilities beyond those utilities but I just can't find
PL> anything out there for the old format dBase3 stuff.  Any ideas?
There  is  a small public domain program named Memopack that 
is quite efficient to pack DBT file and repair some  pointer 
problems.  It  was  written by John T. Opincar. The C source 
code of that program is given.  You can easily recompile it.  
An OBJ  is  also  available  for  those  who  don't  have  C 
compiler. Here is the C Source code:
---------------------------------------------------------------------------
/*  John T. Opincar, Jr. [OpinSoft]
	CID 71631,541
	03/31/91
	Donated to the public domain
	This module implements a "safe" and fast memo packing function.  
	Memopack() is safe because it can tolerate a power loss at any time
	without leaving your database/memo file in an inconsistent state.  At
	worst, you'll lose ONE memo field, and only if the power loss/three-finger
	salute occurs within a very short window.  Memopack() is also fast--
	in tests that I ran, it ran in about 70% of the time COPY TO required.
	Another benefit is that it only uses two file handles and does not 
	require that you have enough disk space to make a temporary copy of the 
	*.dbf file.
	In the worst case scenario, Memopack() will need the size of the 
	original *.dbt file available on disk.  This occurs when you pack
	a memo file which is already in optimal condition.
	To use Memopack(), simply include memopack.obj in your link file.  You
	invoke memopack with the following syntax: memopack(sDbfName).
	If you are wondering how Memopack() can be "safe," (or you are skeptical
	by nature) then read on.  Otherwise, you really don't need to know
	anymore to use the function.  Memopack() is fault tolerant because at
	any stage of its execution, the memo field pointers in the database
	always point to a valid memo field.  The validity of the pointers is
	preserved by using the same DBT file to store a packed copy itself.
	Right after each memo is copied to the end of the DBT file, the database
	pointer is updated.  The first four bytes of the DBT file contain the
	block number of the next available 512 byte block in the file.  This
	pointer is also updated after each memo is written to the bottom of 
	the file, thus the integrity of the DBT file is preserved at all times
	during this process.
	Once all of the memos have been packed to the bottom of the DBT file,
	the packed memos are copied back to the beginning of the file.  Since
	the memo pointers in the DBF are still pointing to the copies at the
	bottom of the file, an interruption at this stage will not cause any
	problems.  Of course, the DBT would then be larger than before the
	pack was started, but you would just run memopack() again (hopefully
	without interruption this time) to shrink it down to the minimal size.
	Once the packed memos have been copied to the top of the DBT file, 
	all of the memo pointers in the DBF file are adjusted to point to the
	packed copies.  The final step is to reset the pointer at the top of the
	DBT file and then truncate the DBT file to its new, optimal size.
*/
/* If you need to recompile for some reason, make sure you set it up for the
   appropriate version of Clipper.
*/
#define S87 0
#ifdef S87
	#include "nandef.h"
#endif
#include "extend.h"
extern int _topen(byte *, int);
extern int _tclose(int);
extern int _tread(int, byte *, int);
extern int _twrite(int, byte *, int);
extern long _tlseek(int, long, int);
/*---------------------------------READNUM----------------------------------*/
/*
PURPOSE: Reads int or long from file
RETURNS: Long
*/
long readnum(handle, bytecount)
int handle, bytecount;
{
    byte abyte;
    int i;
    long numread = 0;
    for (i = 0; i < bytecount; i++) {
        _tread(handle, &abyte, 1);
        numread = numread | (((long)abyte) << (i << 3));
    }
    return( numread );
}
/*---------------------------------WRITENUM---------------------------------*/
/*
PURPOSE: Writes int or long to file
*/
void writenum(handle, number, bytecount)
int handle;
long number;
int bytecount;
{
    int i;
    byte abyte;
    for (i = 0; i < bytecount; i++) {
        abyte = (byte)(number & 0x000000FF);
        _twrite(handle, &abyte, 1);
        number = (number / 256);
    }
    return;
}
/*----------------------------------MSTOL-----------------------------------*/
/*
PURPOSE: Converts hokey ASCII representation of memo block pointers in
		 database memo fields into a long
RETURNS: Long
*/
long mstol(memoptrstr)
byte *memoptrstr;
{
	int i;
	long memoptr;
	for (i = 0, memoptr = 0; i <= 9; i++) {
		if ( memoptrstr[i] == 32 ) {
			memoptr = memoptr * 10;
		} else {
			memoptr = (memoptr * 10) + memoptrstr[i] - 48;
		}
	}
	return( memoptr );
}
/*----------------------------------LTOMS-----------------------------------*/
/*
PURPOSE: Converts long into hokey ASCII representation of memo block pointers
		 in database field
RETURNS: Returns string in memostr
*/
void ltoms(memoptr, memostr)
long memoptr;
byte *memostr;
{
	int i;
	for (i = 0; i <= 9; i++) memostr[i] = ' ';
	for (i = 9; (i >= 0) && (memoptr > 0); i--) {
		memostr[i] = (memoptr % 10) + 48;
		memoptr = memoptr / 10;
	}	
}
/*---------------------------------MSTRINC----------------------------------*/
/*
PURPOSE: Increments hokey ASCII representation of memo block pointers used
		 in database memo fields
RETURNS: Pointer to string
*/
byte *mstrinc(memostr)
byte *memostr;
{
	int i = 9, carry = 1;
	while ( carry ) {
		carry = 0;
		memostr[i]++;
		if ( memostr[i] > '9' ) {
			memostr[i--] = '0';
			carry = 1;
		}
	}
	return( memostr );
}
/*---------------------------------MEMOPACK---------------------------------*/
/*
PURPOSE: The real McCoy.
RETURNS: .T. if memo file pack was successful
*/
CLIPPER MEMOPACK()
{
	byte dbfname[13], dbtname[13], *s, *d, *t, signature;
	byte fieldname[11], fieldtype, memoptrstr[10], *memoblock;
	byte nextstr[10], startptrstr[10], fielddec;
	int dbfhandle, dbthandle, fieldlen, offset, headersize, recordsize;
	int offsets[64], memocount, curmemo, i;
	long recordcount, memoptr, recordptr, currecord, firstblock, nextblock;
	long blockcount, source, dest;
	/* get and fix up file names */
	s = _parc(1);
	d = dbfname;
	t = dbtname;
	while ( *s && (*s != '.') ) {
		*d++ = *s;
		*t++ = *s++;
	}
	*d++ = '.'; *d++ = 'd'; *d++ = 'b'; *d++ = 'f'; *d = '\0';
	*t++ = '.'; *t++ = 'd'; *t++ = 'b'; *t++ = 't'; *t = '\0';
	/* open and verify files */
	if ( (dbfhandle = _topen(dbfname, 2)) < 0 ) {
		_retl(0);
		return;
	}
	_tread(dbfhandle, &signature, 1);
	if ( signature != 0x83 ) {
		_tclose(dbfhandle);
		_retl(0);
		return;
	}
	if ( (dbthandle = _topen(dbtname, 2)) < 0 ) {
		_tclose(dbfhandle);
		_retl(0);
		return;
	}
	/* read some essential dbf info */
   	_tlseek(dbfhandle, (long)4, 0);
    recordcount = readnum(dbfhandle, 4);
    headersize = readnum(dbfhandle, 2);
    recordsize = (int)readnum(dbfhandle, 2);
	/* allocate a read/write buffer */
	memoblock = _exmgrab(512);
	/* calculate the offsets to the memo fields from the dbf header */
	_tlseek(dbfhandle, (long)32, 0);
	_tread(dbfhandle, fieldname, 11);
	offset = 1;
	memocount = -1;
	while ( fieldname[0] != (char)13 ) {
		_tread(dbfhandle, &fieldtype, 1);
		_tlseek(dbfhandle, (long)4, 1);
		_tread(dbfhandle, &fielddec, 1);
		fieldlen = fielddec;
		_tread(dbfhandle, &fielddec, 1);
		if ( (fieldtype == 'C') && fielddec ) {
			fieldlen = (fielddec << 8) | fieldlen;
			fielddec = 0;
		}
		if ( fieldtype == 'M' ) {
			offsets[++memocount] = offset;
		}
		offset += fieldlen;
		_tlseek(dbfhandle, (long)14, 1);
		_tread(dbfhandle, fieldname, 11);
	}
	/* Pack everything to past the former end of the memo file, adjusting */
	/* the pointers in the dbf as we go.  Also adjust the pointer in the 
	/* memo file to the last block as we go. */
	_tlseek(dbthandle, (long)0, 0);
	firstblock = readnum(dbthandle, 4);
	nextblock = firstblock;
	ltoms(nextblock, nextstr);
	blockcount = 0;
	recordptr = headersize;
	/* for each record in the dbf */
	for (currecord = 0; currecord < recordcount; currecord++) {
		/* for each memo field in the record */
		for (curmemo = 0; curmemo <= memocount; curmemo++) {
			_tlseek(dbfhandle, recordptr + (long)offsets[curmemo], 0);
			_tread(dbfhandle, memoptrstr, 10);
			memoptr = mstol(memoptrstr) << 9;
			if ( memoptr > 0 ) {
				for (i = 0, s = nextstr, d = startptrstr; i <= 9; i++) *d++ = *s++;
				i = 512;
				/* for each 512 byte block allocated to this particular memo */
				while ( i >= 512 ) {
					blockcount++;
					_tlseek(dbthandle, memoptr, 0);
					_tread(dbthandle, memoblock, 512);
					_tlseek(dbthandle, nextblock << 9, 0);
					_twrite(dbthandle, memoblock, 512);
					nextblock++;
					mstrinc(nextstr);
					i = 0;
					s = memoblock;
					while ( (i < 512) && (*s != 26) ) {
						i++;
						s++;
					}
					memoptr += (long)512;
				}
				/* avoid disaster by cleaning up after each memo field */
				_tlseek(dbthandle, (long)0, 0);
				writenum(dbthandle, nextblock, 4);
				_tlseek(dbfhandle, recordptr + (long)offsets[curmemo], 0);
				_twrite(dbfhandle, startptrstr, 10);
			}
		}
		recordptr += recordsize;
	}
	/* Now that everything is safely packed at the end of the memo file, */
	/* we have to move it back to the top.  Note that we don't adjust the */
	/* memo pointers in the dbf yet */
	source = firstblock << 9;
	dest = (long)512;
	for (i = 1; i <= blockcount; i++) {
		_tlseek(dbthandle, source, 0);
		_tread(dbthandle, memoblock, 512);
		_tlseek(dbthandle, dest, 0);
		_twrite(dbthandle, memoblock, 512);
		source += (long)512;
		dest += (long)512;
	}
	/* Now we can adjust the memo pointers in the dbf */
	recordptr = headersize;
	for (currecord = 0; currecord < recordcount; currecord++) {
		for (curmemo = 0; curmemo <= memocount; curmemo++) {
			_tlseek(dbfhandle, recordptr + (long)offsets[curmemo], 0);
			_tread(dbfhandle, memoptrstr, 10);
			nextblock = mstol(memoptrstr);
			ltoms(nextblock - firstblock + 1, memoptrstr);
			_tlseek(dbfhandle, recordptr + (long)offsets[curmemo], 0);
			_twrite(dbfhandle, memoptrstr, 10);
		}
		recordptr += recordsize;
	}
	/* Finally, we gleefully chop off all of that wasted space! */
	_tlseek(dbthandle, (long)0, 0);
	writenum(dbthandle, (dest >> 9), 4);
	_tlseek(dbthandle, dest + 1, 0);
	_twrite(dbthandle, memoptrstr, 0);
	_tclose(dbfhandle);
	_tclose(dbthandle);
	_exmback(memoblock, 512);
	_retl(1);
}
---------------------------------------------------------------------------
Salut Paul,
Pierre Forest
Fidonet: 1:167/136        Internet: pforest@absint.login.qc.ca       
Compuserve: 71722,1057    FrancoMedia: 101:161/300
--- FMail/386 1.02+
---------------
* Origin: ABS International, Montreal, Can. * 937.7451 (1:167/136)

SOURCE: echomail via exec-pc

Email questions or comments to sysop@ipingthereforeiam.com
All parts of this website painstakingly hand-crafted in the U.S.A.!
IPTIA BBS/MUD/Terminal/Game Server List, © 2025 IPTIA Consulting™.