Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to repair a corrupted DBF header?

Databases and tables

How to repair a corrupted DBF header?

by  dbMark  Posted    (Edited  )
It's important to note that this function only repairs an invalid table header record count, it does not resolve any other issues in the header or table records.

faq184-3162 and faq184-4353 give background on the format of a DBF table's header. In my experience corrupted tables are rare but usually encountered after a partially completed APPEND operation, probably in a multi-user or networked environment. What that means is that VFP appended the record(s) but for whatever reason failed to update the header record with the new record count. So the next time a user tries to open the table then error #2091 is generated and the user sees the message: [color #204A87]Table "name" has become corrupted. The table will need to be repaired before using again.[/color]

While the message can be bypassed by changing the setting of SET("TABLEVALIDATE") from the normal 3 to 0, that only bypasses the error, the user can open the table but the table header is still "corrupted", that is, incorrect.

When I first wrote it I used FILETOSTR() and STRTOFILE() but while simple this does read and rewrite the entire table. It's best to make as minimal an impact as possible, especially with large tables so I wanted to update only the 4 bytes holding the record count. I instead used the low level file functions such as FOPEN, FSEEK, FREAD, FWRITE and FCLOSE that are present in all versions of Visual FoxPro.

Code:
FUNCTION FixRecCount( pTable )
LOCAL cTable, objErr, nErrNo, cErrMsg, nTmp, nTmpCount, nHandle, nAscValue, nSuccess
LOCAL nTabLen, cHdrRead, nCurCount, nHdrLen, nRecLen, nRecCount, cHdrWrite
DO CASE
CASE !EMPTY(pTable) AND FILE(pTable)
	cTable = pTable
CASE !EMPTY(pTable) AND FILE(pTable + ".dbf")
	cTable = pTable + ".dbf"
OTHERWISE
	cTable = GETFILE("DBF")
ENDCASE

nErrNo = 0
cErrMsg = ""
SET TABLEVALIDATE TO 3  && normal table validation
IF FILE(cTable)

	TRY
		USE (cTable) IN 0 ALIAS _OPENTEST SHARED
	CATCH TO objErr
		nErrNo = objErr.ErrorNo
		cErrMsg = objErr.Message
		RELEASE objErr
	ENDTRY
	IF NOT EMPTY(SELECT("_OPENTEST"))
		USE IN "_OPENTEST"
	ENDIF

	IF nErrNo = 2091 OR "corrupted" $ cErrMsg

		nHandle = FOPEN(cTable, 2)
		IF nHandle <> 0
			nTabLen = FSEEK(nHandle, 0, 2) && go to EOF to determine file size
			IF nTabLen <> 0
				nTmp = FSEEK(nHandle, 0, 0) && go to BOF()
				* We only need data from the first 12 bytes of the header
				cHdrRead = FREAD(nHandle, 12)

				nCurCount = ASC(SUBSTR(cHdrRead,5,1)) * (256^0) + ASC(SUBSTR(cHdrRead,6,1)) * (256^1) + ASC(SUBSTR(cHdrRead,7,1)) * (256^2) + ASC(SUBSTR(cHdrRead,8,1)) * (256^3)
				nHdrLen = ASC(SUBSTR(cHdrRead,9,1)) * (256^0) + ASC(SUBSTR(cHdrRead,10,1)) * (256^1) && Header length
				nRecLen = ASC(SUBSTR(cHdrRead,11,1)) * (256^0) + ASC(SUBSTR(cHdrRead,12,1)) * (256^1) && Record Length
				nRecCount = (nTabLen - (nHdrLen + 1)) / nRecLen

				nAscValue = INT(nRecCount/(256^3))
				nTmpCount = MOD(nRecCount, 256^3)
				cHdrWrite = CHR(nAscValue)

				nAscValue = INT(nTmpCount/(256^2))
				nTmpCount = MOD(nTmpCount, 256^2)
				cHdrWrite = CHR(nAscValue) + cHdrWrite

				nAscValue = INT(nTmpCount/(256^1))
				nTmpCount = MOD(nTmpCount, 256^1)
				cHdrWrite = CHR(nAscValue) + cHdrWrite

				nAscValue = INT(nTmpCount)
				cHdrWrite = CHR(nAscValue) + cHdrWrite

				IF nCurCount <> nRecCount
					MESSAGEBOX("Table record count ("+LTRIM(STR(nCurCount))+") will be updated to "+LTRIM(STR(nRecCount))+".", 48, "Record count incorrect")
					nTmp = FSEEK(nHandle, 4, 0) && BOF() + 4
					nSuccess = FWRITE(nHandle, cHdrWrite, 12)
					MESSAGEBOX("Table record count was " + IIF(EMPTY(nSuccess), "NOT ", "") + "updated to "+LTRIM(STR(nRecCount))+".", 48, "Write results")
				ELSE
					MESSAGEBOX("Table record count ("+LTRIM(STR(nCurCount))+") is correct.", 48, "No action needed")
				ENDIF

			ELSE
				MESSAGEBOX("Cannot low-level open table.", 48, "Unable to continue.")
			ENDIF
			nTmp = FCLOSE(nHandle)
		ELSE
			MESSAGEBOX("File has zero size or unable to properly open table", 64, "Table not open")
		ENDIF
	ELSE
		MESSAGEBOX("No table validation errors", 64, "No issues detected")
	ENDIF
ELSE
	MESSAGEBOX("No table selected", 64, "Nothing to do")
ENDIF
RETURN
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top