mariadb/storage/connect/filamtxt.cpp
Mikhail Chalov 2ff01e763e Fix insecure use of strcpy, strcat and sprintf in Connect
Old style C functions `strcpy()`, `strcat()` and `sprintf()` are vulnerable to
security issues due to lacking memory boundary checks. Replace these in the
Connect storage engine with safe new and/or custom functions such as
`snprintf()` `safe_strcpy()` and `safe_strcat()`.

With this change FlawFinder and other static security analyzers report 287
fewer findings.

All new code of the whole pull request, including one or several files that are
either new files or modified ones, are contributed under the BSD-new license. I
am contributing on behalf of my employer Amazon Web Services, Inc.
2023-05-12 15:37:00 +01:00

2123 lines
70 KiB
C++

/*********** File AM Txt C++ Program Source Code File (.CPP) ***********/
/* PROGRAM NAME: FILAMTXT */
/* ------------- */
/* Version 1.8 */
/* */
/* COPYRIGHT: */
/* ---------- */
/* (C) Copyright to the author Olivier BERTRAND 2005-2020 */
/* */
/* WHAT THIS PROGRAM DOES: */
/* ----------------------- */
/* This program are the Text file access method classes. */
/* */
/***********************************************************************/
/***********************************************************************/
/* Include relevant sections of the System header files. */
/***********************************************************************/
#include "my_global.h"
#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#include <errno.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__ // To define min/max as macro
#endif // __BORLANDC__
//#include <windows.h>
#else // !_WIN32
#if defined(UNIX) || defined(UNIV_LINUX)
#include <errno.h>
#include <unistd.h>
//#if !defined(sun) // Sun has the ftruncate fnc.
//#define USETEMP // Force copy mode for DELETE
//#endif // !sun
#else // !UNIX
#include <io.h>
#endif // !UNIX
#include <fcntl.h>
#endif // !_WIN32
#include <m_string.h>
/***********************************************************************/
/* Include application header files: */
/* global.h is header containing all global declarations. */
/* plgdbsem.h is header containing the DB application declarations. */
/* filamtxt.h is header containing the file AM classes declarations. */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "filamtxt.h"
#include "tabdos.h"
#include "tabjson.h"
#if defined(UNIX) || defined(UNIV_LINUX)
#include "osutil.h"
#define _fileno fileno
#define _O_RDONLY O_RDONLY
#endif
extern int num_read, num_there, num_eq[2]; // Statistics
/***********************************************************************/
/* Routine called externally by TXTFAM SortedRows functions. */
/***********************************************************************/
PARRAY MakeValueArray(PGLOBAL g, PPARM pp);
/* --------------------------- Class TXTFAM -------------------------- */
/***********************************************************************/
/* Constructors. */
/***********************************************************************/
TXTFAM::TXTFAM(PDOSDEF tdp)
{
Tdbp = NULL;
To_Fb = NULL;
if (tdp) {
To_File = tdp->Fn;
Lrecl = tdp->Lrecl;
Eof = tdp->Eof;
Ending = tdp->Ending;
} else {
To_File = NULL;
Lrecl = 0;
Eof = false;
#if defined(_WIN32)
Ending = 2;
#else
Ending = 1;
#endif
} // endif tdp
Placed = false;
IsRead = true;
Blocked = false;
To_Buf = NULL;
DelBuf = NULL;
BlkPos = NULL;
To_Pos = NULL;
To_Sos = NULL;
To_Upd = NULL;
Posar = NULL;
Sosar = NULL;
Updar = NULL;
BlkLen = 0;
Buflen = 0;
Dbflen = 0;
Rows = 0;
DelRows = 0;
Headlen = 0;
Block = 0;
Last = 0;
Nrec = 1;
OldBlk = -1;
CurBlk = -1;
ReadBlks = 0;
CurNum = 0;
Rbuf = 0;
Modif = 0;
Blksize = 0;
Fpos = Spos = Tpos = 0;
Padded = false;
Abort = false;
CrLf = (char*)(Ending == 1 ? "\n" : "\r\n");
} // end of TXTFAM standard constructor
TXTFAM::TXTFAM(PTXF txfp)
{
Tdbp = txfp->Tdbp;
To_Fb = txfp->To_Fb;
To_File = txfp->To_File;
Lrecl = txfp->Lrecl;
Placed = txfp->Placed;
IsRead = txfp->IsRead;
Blocked = txfp->Blocked;
To_Buf = txfp->To_Buf;
DelBuf = txfp->DelBuf;
BlkPos = txfp->BlkPos;
To_Pos = txfp->To_Pos;
To_Sos = txfp->To_Sos;
To_Upd = txfp->To_Upd;
Posar = txfp->Posar;
Sosar = txfp->Sosar;
Updar = txfp->Updar;
BlkLen = txfp->BlkLen;
Buflen = txfp->Buflen;
Dbflen = txfp->Dbflen;
Rows = txfp->Rows;
DelRows = txfp->DelRows;
Headlen = txfp->Headlen;
Block = txfp->Block;
Last = txfp->Last;
Nrec = txfp->Nrec;
OldBlk = txfp->OldBlk;
CurBlk = txfp->CurBlk;
ReadBlks = txfp->ReadBlks;
CurNum = txfp->CurNum;
Rbuf = txfp->Rbuf;
Modif = txfp->Modif;
Blksize = txfp->Blksize;
Fpos = txfp->Fpos;
Spos = txfp->Spos;
Tpos = txfp->Tpos;
Padded = txfp->Padded;
Eof = txfp->Eof;
Ending = txfp->Ending;
Abort = txfp->Abort;
CrLf = txfp->CrLf;
} // end of TXTFAM copy constructor
/***********************************************************************/
/* Reset: reset position values at the beginning of file. */
/***********************************************************************/
void TXTFAM::Reset(void)
{
Rows = 0;
DelRows = 0;
OldBlk = -1;
CurBlk = -1;
ReadBlks = 0;
CurNum = 0;
Rbuf = 0;
Modif = 0;
Placed = false;
} // end of Reset
/***********************************************************************/
/* TXT GetFileLength: returns file size in number of bytes. */
/***********************************************************************/
int TXTFAM::GetFileLength(PGLOBAL g)
{
char filename[_MAX_PATH];
int h;
int len;
PlugSetPath(filename, To_File, Tdbp->GetPath());
h= global_open(g, MSGID_OPEN_MODE_STRERROR, filename, _O_RDONLY);
if (trace(1))
htrc("GetFileLength: fn=%s h=%d\n", filename, h);
if (h == -1) {
if (errno != ENOENT) {
if (trace(1))
htrc("%s\n", g->Message);
len = -1;
} else {
len = 0; // File does not exist yet
g->Message[0]= '\0';
} // endif errno
} else {
if ((len = _filelength(h)) < 0)
snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", filename);
if (Eof && len)
len--; // Do not count the EOF character
close(h);
} // endif h
return len;
} // end of GetFileLength
/***********************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/* Note: This function is meant only for fixed length files but is */
/* placed here to be available to FIXFAM and MPXFAM classes. */
/***********************************************************************/
int TXTFAM::Cardinality(PGLOBAL g)
{
if (g) {
int card = -1;
int len = GetFileLength(g);
if (len >= 0) {
if (Padded && Blksize) {
if (!(len % Blksize))
card = (len / Blksize) * Nrec;
else
snprintf(g->Message, sizeof(g->Message), MSG(NOT_FIXED_LEN), To_File, len, Lrecl);
} else {
if (!(len % Lrecl))
card = len / (int)Lrecl; // Fixed length file
else
snprintf(g->Message, sizeof(g->Message), MSG(NOT_FIXED_LEN), To_File, len, Lrecl);
} // endif Padded
if (trace(1))
htrc(" Computed max_K=%d Filen=%d lrecl=%d\n",
card, len, Lrecl);
} else
card = 0;
// Set number of blocks for later use
Block = (card > 0) ? (card + Nrec - 1) / Nrec : 0;
return card;
} else
return 1;
} // end of Cardinality
/***********************************************************************/
/* Use BlockTest to reduce the table estimated size. */
/* Note: This function is meant only for fixed length files but is */
/* placed here to be available to FIXFAM and MPXFAM classes. */
/***********************************************************************/
int TXTFAM::MaxBlkSize(PGLOBAL g, int s)
{
int rc = RC_OK, savcur = CurBlk, blm1 = Block - 1;
int size, last = s - blm1 * Nrec;
// Roughly estimate the table size as the sum of blocks
// that can contain good rows
for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++)
if ((rc = Tdbp->TestBlock(g)) == RC_OK)
size += (CurBlk == blm1) ? last : Nrec;
else if (rc == RC_EF)
break;
CurBlk = savcur;
return size;
} // end of MaxBlkSize
/***********************************************************************/
/* AddListValue: Used when doing indexed update or delete. */
/***********************************************************************/
bool TXTFAM::AddListValue(PGLOBAL g, int type, void *val, PPARM *top)
{
PPARM pp = (PPARM)PlugSubAlloc(g, NULL, sizeof(PARM));
switch (type) {
// case TYPE_INT:
// pp->Value = PlugSubAlloc(g, NULL, sizeof(int));
// *((int*)pp->Value) = *((int*)val);
// break;
case TYPE_VOID:
pp->Intval = *(int*)val;
break;
// case TYPE_STRING:
// pp->Value = PlugDup(g, (char*)val);
// break;
case TYPE_PCHAR:
pp->Value = val;
break;
default:
return true;
} // endswitch type
pp->Type = type;
pp->Domain = 0;
pp->Next = *top;
*top = pp;
return false;
} // end of AddListValue
/***********************************************************************/
/* Store needed values for indexed UPDATE or DELETE. */
/***********************************************************************/
int TXTFAM::StoreValues(PGLOBAL g, bool upd)
{
int pos = GetPos();
bool rc = AddListValue(g, TYPE_VOID, &pos, &To_Pos);
if (!rc) {
pos = GetNextPos();
rc = AddListValue(g, TYPE_VOID, &pos, &To_Sos);
} // endif rc
if (upd && !rc) {
char *buf;
if (Tdbp->PrepareWriting(g))
return RC_FX;
buf = PlugDup(g, Tdbp->GetLine());
rc = AddListValue(g, TYPE_PCHAR, buf, &To_Upd);
} // endif upd
return rc ? RC_FX : RC_OK;
} // end of StoreValues
/***********************************************************************/
/* UpdateSortedRows. When updating using indexing, the issue is that */
/* record are not necessarily updated in sequential order. */
/* Moving intermediate lines cannot be done while making them because */
/* this can cause extra wrong records to be included in the new file. */
/* What we do here is to reorder the updated records and do all the */
/* updates ordered by record position. */
/***********************************************************************/
int TXTFAM::UpdateSortedRows(PGLOBAL g)
{
int *ix, i;
/*********************************************************************/
/* Get the stored update values and sort them. */
/*********************************************************************/
if (!(Posar = MakeValueArray(g, To_Pos))) {
// strcpy(g->Message, "Position array is null");
// return RC_INFO;
return RC_OK; // Nothing to do
} else if (!(Sosar = MakeValueArray(g, To_Sos))) {
snprintf(g->Message, sizeof(g->Message), "Start position array is null");
goto err;
} else if (!(Updar = MakeValueArray(g, To_Upd))) {
snprintf(g->Message, sizeof(g->Message), "Updated line array is null");
goto err;
} else if (!(ix = (int*)Posar->GetSortIndex(g))) {
snprintf(g->Message, sizeof(g->Message), "Error getting array sort index");
goto err;
} // endif's
Rewind();
for (i = 0; i < Posar->GetNval(); i++) {
SetPos(g, Sosar->GetIntValue(ix[i]));
Fpos = Posar->GetIntValue(ix[i]);
strcpy(Tdbp->To_Line, Updar->GetStringValue(ix[i]));
// Now write the updated line.
if (WriteBuffer(g))
goto err;
} // endfor i
return RC_OK;
err:
if (trace(1))
htrc("%s\n", g->Message);
return RC_FX;
} // end of UpdateSortedRows
/***********************************************************************/
/* DeleteSortedRows. When deleting using indexing, the issue is that */
/* record are not necessarily deleted in sequential order. Moving */
/* intermediate lines cannot be done while deleing them because */
/* this can cause extra wrong records to be included in the new file. */
/* What we do here is to reorder the deleted record and delete from */
/* the file from the ordered deleted records. */
/***********************************************************************/
int TXTFAM::DeleteSortedRows(PGLOBAL g)
{
int *ix, i, irc;
/*********************************************************************/
/* Get the stored delete values and sort them. */
/*********************************************************************/
if (!(Posar = MakeValueArray(g, To_Pos))) {
// strcpy(g->Message, "Position array is null");
// return RC_INFO;
return RC_OK; // Nothing to do
} else if (!(Sosar = MakeValueArray(g, To_Sos))) {
snprintf(g->Message, sizeof(g->Message), "Start position array is null");
goto err;
} else if (!(ix = (int*)Posar->GetSortIndex(g))) {
snprintf(g->Message, sizeof(g->Message), "Error getting array sort index");
goto err;
} // endif's
Tpos = Spos = 0;
for (i = 0; i < Posar->GetNval(); i++) {
if ((irc = InitDelete(g, Posar->GetIntValue(ix[i]),
Sosar->GetIntValue(ix[i]))) == RC_FX)
goto err;
// Now delete the sorted rows
if (DeleteRecords(g, irc))
goto err;
} // endfor i
return RC_OK;
err:
if (trace(1))
htrc("%s\n", g->Message);
return RC_FX;
} // end of DeleteSortedRows
/***********************************************************************/
/* The purpose of this function is to deal with access methods that */
/* are not coherent regarding the use of SetPos and GetPos. */
/***********************************************************************/
int TXTFAM::InitDelete(PGLOBAL g, int, int)
{
snprintf(g->Message, sizeof(g->Message), "InitDelete should not be used by this table type");
return RC_FX;
} // end of InitDelete
/* --------------------------- Class DOSFAM -------------------------- */
/***********************************************************************/
/* Constructors. */
/***********************************************************************/
DOSFAM::DOSFAM(PDOSDEF tdp) : TXTFAM(tdp)
{
To_Fbt = NULL;
Stream = NULL;
T_Stream = NULL;
UseTemp = false;
Bin = false;
} // end of DOSFAM standard constructor
DOSFAM::DOSFAM(PDOSFAM tdfp) : TXTFAM(tdfp)
{
To_Fbt = tdfp->To_Fbt;
Stream = tdfp->Stream;
T_Stream = tdfp->T_Stream;
UseTemp = tdfp->UseTemp;
Bin = tdfp->Bin;
} // end of DOSFAM copy constructor
DOSFAM::DOSFAM(PBLKFAM tdfp, PDOSDEF tdp) : TXTFAM(tdp)
{
Tdbp = tdfp->Tdbp;
To_Fb = tdfp->To_Fb;
To_Fbt = tdfp->To_Fbt;
Stream = tdfp->Stream;
T_Stream = tdfp->T_Stream;
UseTemp = tdfp->UseTemp;
Bin = tdfp->Bin;
} // end of DOSFAM constructor from BLKFAM
/***********************************************************************/
/* Reset: reset position values at the beginning of file. */
/***********************************************************************/
void DOSFAM::Reset(void)
{
TXTFAM::Reset();
Bin = false;
Fpos = Tpos = Spos = 0;
} // end of Reset
/***********************************************************************/
/* DOS GetFileLength: returns file size in number of bytes. */
/***********************************************************************/
int DOSFAM::GetFileLength(PGLOBAL g)
{
int len;
if (!Stream)
len = TXTFAM::GetFileLength(g);
else
if ((len = _filelength(_fileno(Stream))) < 0)
snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", To_File);
if (trace(1))
htrc("File length=%d\n", len);
return len;
} // end of GetFileLength
/***********************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/***********************************************************************/
int DOSFAM::Cardinality(PGLOBAL g)
{
return (g) ? -1 : 0;
} // end of Cardinality
/***********************************************************************/
/* Use BlockTest to reduce the table estimated size. */
/* Note: This function is not really implemented yet. */
/***********************************************************************/
int DOSFAM::MaxBlkSize(PGLOBAL, int s)
{
return s;
} // end of MaxBlkSize
/***********************************************************************/
/* OpenTableFile: Open a DOS/UNIX table file using C standard I/Os. */
/***********************************************************************/
bool DOSFAM::OpenTableFile(PGLOBAL g)
{
char opmode[4], filename[_MAX_PATH];
//int ftype = Tdbp->GetFtype();
MODE mode = Tdbp->Mode;
PDBUSER dbuserp = PlgGetUser(g);
// This is required when using Unix files under Windows and vice versa
//Bin = (Blocked || Ending != CRLF);
Bin = true; // To avoid ftell problems
switch (mode) {
case MODE_READ:
snprintf(opmode, sizeof(opmode), "r");
break;
case MODE_DELETE:
if (!Tdbp->Next) {
// Store the number of deleted lines
DelRows = Cardinality(g);
if (Blocked) {
// Cardinality must return 0
Block = 0;
Last = Nrec;
} // endif blocked
// This will erase the entire file
snprintf(opmode, sizeof(opmode), "w");
Tdbp->ResetSize();
break;
} // endif
// Selective delete, pass thru
Bin = true;
/* fall through */
case MODE_UPDATE:
if ((UseTemp = Tdbp->IsUsingTemp(g))) {
snprintf(opmode, sizeof(opmode), "r");
Bin = true;
} else
snprintf(opmode, sizeof(opmode), "r+");
break;
case MODE_INSERT:
snprintf(opmode, sizeof(opmode), "a+");
break;
default:
snprintf(g->Message, sizeof(g->Message), MSG(BAD_OPEN_MODE), mode);
return true;
} // endswitch Mode
// For blocked I/O or for moving lines, open the table in binary
safe_strcat(opmode, sizeof(opmode), (Bin) ? "b" : "t");
// Now open the file stream
PlugSetPath(filename, To_File, Tdbp->GetPath());
if (!(Stream = PlugOpenFile(g, filename, opmode))) {
if (trace(1))
htrc("%s\n", g->Message);
return (mode == MODE_READ && errno == ENOENT)
? PushWarning(g, Tdbp) : true;
} // endif Stream
if (trace(1))
htrc("File %s open Stream=%p mode=%s\n", filename, Stream, opmode);
To_Fb = dbuserp->Openlist; // Keep track of File block
/*********************************************************************/
/* Allocate the line buffer. For mode Delete a bigger buffer has to */
/* be allocated because is it also used to move lines into the file.*/
/*********************************************************************/
return AllocateBuffer(g);
} // end of OpenTableFile
/***********************************************************************/
/* Allocate the line buffer. For mode Delete a bigger buffer has to */
/* be allocated because is it also used to move lines into the file. */
/***********************************************************************/
bool DOSFAM::AllocateBuffer(PGLOBAL g)
{
MODE mode = Tdbp->Mode;
// Lrecl does not include line ending
Buflen = Lrecl + Ending + ((Bin) ? 1 : 0) + 1; // Sergei
if (trace(1))
htrc("SubAllocating a buffer of %d bytes\n", Buflen);
To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
if (UseTemp || mode == MODE_DELETE) {
// Have a big buffer to move lines
Dbflen = Buflen * DOS_BUFF_LEN;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} else if (mode == MODE_INSERT) {
/*******************************************************************/
/* Prepare the buffer so eventual gaps are filled with blanks. */
/*******************************************************************/
memset(To_Buf, ' ', Buflen);
To_Buf[Buflen - 2] = '\n';
To_Buf[Buflen - 1] = '\0';
} // endif's mode
return false;
} // end of AllocateBuffer
/***********************************************************************/
/* GetRowID: return the RowID of last read record. */
/***********************************************************************/
int DOSFAM::GetRowID(void)
{
return Rows;
} // end of GetRowID
/***********************************************************************/
/* GetPos: return the position of last read record. */
/***********************************************************************/
int DOSFAM::GetPos(void)
{
return Fpos;
} // end of GetPos
/***********************************************************************/
/* GetNextPos: return the position of next record. */
/***********************************************************************/
int DOSFAM::GetNextPos(void)
{
return ftell(Stream);
} // end of GetNextPos
/***********************************************************************/
/* SetPos: Replace the table at the specified position. */
/***********************************************************************/
bool DOSFAM::SetPos(PGLOBAL g, int pos)
{
Fpos = pos;
if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), Fpos);
return true;
} // endif
Placed = true;
return false;
} // end of SetPos
/***********************************************************************/
/* Record file position in case of UPDATE or DELETE. */
/***********************************************************************/
bool DOSFAM::RecordPos(PGLOBAL g)
{
if ((Fpos = ftell(Stream)) < 0) {
snprintf(g->Message, sizeof(g->Message), MSG(FTELL_ERROR), 0, strerror(errno));
// strcat(g->Message, " (possible wrong ENDING option value)");
return true;
} // endif Fpos
return false;
} // end of RecordPos
/***********************************************************************/
/* Initialize Fpos and the current position for indexed DELETE. */
/***********************************************************************/
int DOSFAM::InitDelete(PGLOBAL g, int fpos, int spos)
{
Fpos = fpos;
if (fseek(Stream, spos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), Fpos);
return RC_FX;
} // endif
return RC_OK;
} // end of InitDelete
/***********************************************************************/
/* Skip one record in file. */
/***********************************************************************/
int DOSFAM::SkipRecord(PGLOBAL g, bool header)
{
PDBUSER dup = (PDBUSER)g->Activityp->Aptr;
// Skip this record
if (!fgets(To_Buf, Buflen, Stream)) {
if (feof(Stream))
return RC_EF;
#if defined(_WIN32)
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, _strerror(NULL));
#else
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, strerror(0));
#endif
return RC_FX;
} // endif fgets
// Update progress information
dup->ProgCur = GetPos();
if (header) {
// For Delete
Fpos = ftell(Stream);
if (!UseTemp)
Tpos = Spos = Fpos; // No need to move header
} // endif header
#if defined(THREAD)
return RC_NF; // To have progress info
#else
return RC_OK; // To loop locally
#endif
} // end of SkipRecord
/***********************************************************************/
/* ReadBuffer: Read one line for a text file. */
/***********************************************************************/
int DOSFAM::ReadBuffer(PGLOBAL g)
{
char *p;
int rc;
if (!Stream)
return RC_EF;
if (trace(2))
htrc("ReadBuffer: Tdbp=%p To_Line=%p Placed=%d\n",
Tdbp, Tdbp->To_Line, Placed);
if (!Placed) {
/*******************************************************************/
/* Record file position in case of UPDATE or DELETE. */
/*******************************************************************/
next:
if (RecordPos(g))
return RC_FX;
CurBlk = (int)Rows++;
if (trace(2))
htrc("ReadBuffer: CurBlk=%d\n", CurBlk);
/********************************************************************/
/* Check whether optimization on ROWID */
/* can be done, as well as for join as for local filtering. */
/*******************************************************************/
switch (Tdbp->TestBlock(g)) {
case RC_EF:
return RC_EF;
case RC_NF:
// Skip this record
if ((rc = SkipRecord(g, FALSE)) != RC_OK)
return rc;
goto next;
} // endswitch rc
} else
Placed = false;
if (trace(2))
htrc(" About to read: stream=%p To_Buf=%p Buflen=%d Fpos=%d\n",
Stream, To_Buf, Buflen, Fpos);
if (fgets(To_Buf, Buflen, Stream)) {
p = To_Buf + strlen(To_Buf) - 1;
if (trace(2))
htrc(" Read: To_Buf=%p p=%c\n", To_Buf, p);
#if defined(_WIN32)
if (Bin) {
// Data file is read in binary so CRLF remains
#else
if (true) {
// Data files can be imported from Windows (having CRLF)
#endif
if (*p == '\n' || *p == '\r') {
// is this enough for Unix ???
*p = '\0'; // Eliminate ending CR or LF character
if (p > To_Buf) {
// is this enough for Unix ???
p--;
if (*p == '\n' || *p == '\r')
*p = '\0'; // Eliminate ending CR or LF character
} // endif To_Buf
} // endif p
} else if (*p == '\n')
*p = '\0'; // Eliminate ending new-line character
if (trace(2))
htrc(" To_Buf='%s'\n", To_Buf);
strcpy(Tdbp->To_Line, To_Buf);
num_read++;
rc = RC_OK;
} else if (feof(Stream)) {
rc = RC_EF;
} else {
#if defined(_WIN32)
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, _strerror(NULL));
#else
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, strerror(0));
#endif
if (trace(1))
htrc("%s\n", g->Message);
rc = RC_FX;
} // endif's fgets
if (trace(2))
htrc("ReadBuffer: rc=%d\n", rc);
IsRead = true;
return rc;
} // end of ReadBuffer
/***********************************************************************/
/* WriteBuffer: File write routine for DOS access method. */
/***********************************************************************/
int DOSFAM::WriteBuffer(PGLOBAL g)
{
int curpos = 0;
bool moved = true;
// T_Stream is the temporary stream or the table file stream itself
if (!T_Stream) {
if (UseTemp && Tdbp->Mode == MODE_UPDATE) {
if (OpenTempFile(g))
return RC_FX;
} else
T_Stream = Stream;
} // endif T_Stream
if (Tdbp->Mode == MODE_UPDATE) {
/*******************************************************************/
/* Here we simply rewrite a record on itself. There are two cases */
/* were another method should be used, a/ when Update apply to */
/* the whole file, b/ when updating the last field of a variable */
/* length file. The method could be to rewrite a new file, then */
/* to erase the old one and rename the new updated file. */
/*******************************************************************/
curpos = ftell(Stream);
if (trace(1))
htrc("Last : %d cur: %d\n", Fpos, curpos);
if (UseTemp) {
/*****************************************************************/
/* We are using a temporary file. */
/* Before writing the updated record, we must eventually copy */
/* all the intermediate records that have not been updated. */
/*****************************************************************/
if (MoveIntermediateLines(g, &moved))
return RC_FX;
Spos = curpos; // New start position
} else
// Update is directly written back into the file,
// with this (fast) method, record size cannot change.
if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0);
return RC_FX;
} // endif
} // endif mode
/*********************************************************************/
/* Prepare the write the updated line. */
/*********************************************************************/
strcat(strcpy(To_Buf, Tdbp->To_Line), (Bin) ? CrLf : "\n");
/*********************************************************************/
/* Now start the writing process. */
/*********************************************************************/
if ((fputs(To_Buf, T_Stream)) == EOF) {
snprintf(g->Message, sizeof(g->Message), MSG(FPUTS_ERROR), strerror(errno));
return RC_FX;
} // endif EOF
if (Tdbp->Mode == MODE_UPDATE && moved)
if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSEEK_ERROR), strerror(errno));
return RC_FX;
} // endif
if (trace(1))
htrc("write done\n");
return RC_OK;
} // end of WriteBuffer
/***********************************************************************/
/* Data Base delete line routine for DOS and BLK access methods. */
/***********************************************************************/
int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
{
bool moved;
int curpos = ftell(Stream);
/*********************************************************************/
/* There is an alternative here: */
/* 1 - use a temporary file in which are copied all not deleted */
/* lines, at the end the original file will be deleted and */
/* the temporary file renamed to the original file name. */
/* 2 - directly move the not deleted lines inside the original */
/* file, and at the end erase all trailing records. */
/* This will be experimented. */
/*********************************************************************/
if (trace(1))
htrc(
"DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
irc, UseTemp, curpos, Fpos, Tpos, Spos);
if (irc != RC_OK) {
/*******************************************************************/
/* EOF: position Fpos at the end-of-file position. */
/*******************************************************************/
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
if (trace(1))
htrc("Fpos placed at file end=%d\n", Fpos);
} // endif irc
if (Tpos == Spos) {
/*******************************************************************/
/* First line to delete, Open temporary file. */
/*******************************************************************/
if (UseTemp) {
if (OpenTempFile(g))
return RC_FX;
} else {
/*****************************************************************/
/* Move of eventual preceding lines is not required here. */
/* Set the target file as being the source file itself. */
/* Set the future Tpos, and give Spos a value to block copying. */
/*****************************************************************/
T_Stream = Stream;
Spos = Tpos = Fpos;
} // endif UseTemp
} // endif Tpos == Spos
/*********************************************************************/
/* Move any intermediate lines. */
/*********************************************************************/
if (MoveIntermediateLines(g, &moved))
return RC_FX;
if (irc == RC_OK) {
/*******************************************************************/
/* Reposition the file pointer and set Spos. */
/*******************************************************************/
if (!UseTemp || moved)
if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0);
return RC_FX;
} // endif
Spos = GetNextPos(); // New start position
if (trace(1))
htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
} else {
/*******************************************************************/
/* Last call after EOF has been reached. */
/* The UseTemp case is treated in CloseTableFile. */
/*******************************************************************/
if (!UseTemp & !Abort) {
/*****************************************************************/
/* Because the chsize functionality is only accessible with a */
/* system call we must close the file and reopen it with the */
/* open function (_fopen for MS ??) this is still to be checked */
/* for compatibility with Text files and other OS's. */
/*****************************************************************/
char filename[_MAX_PATH];
int h; // File handle, return code
PlugSetPath(filename, To_File, Tdbp->GetPath());
/*rc=*/ PlugCloseFile(g, To_Fb);
if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
return RC_FX;
/*****************************************************************/
/* Remove extra records. */
/*****************************************************************/
#if defined(_WIN32)
if (chsize(h, Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(CHSIZE_ERROR), strerror(errno));
close(h);
return RC_FX;
} // endif
#else
if (ftruncate(h, (off_t)Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(TRUNCATE_ERROR), strerror(errno));
close(h);
return RC_FX;
} // endif
#endif
close(h);
if (trace(1))
htrc("done, h=%d irc=%d\n", h, irc);
} // endif !UseTemp
} // endif irc
return RC_OK; // All is correct
} // end of DeleteRecords
/***********************************************************************/
/* Open a temporary file used while updating or deleting. */
/***********************************************************************/
bool DOSFAM::OpenTempFile(PGLOBAL g)
{
char tempname[_MAX_PATH];
bool rc = false;
/*********************************************************************/
/* Open the temporary file, Spos is at the beginning of file. */
/*********************************************************************/
PlugSetPath(tempname, To_File, Tdbp->GetPath());
PlugRemoveType(tempname, tempname);
safe_strcat(tempname, sizeof(tempname), ".t");
if (!(T_Stream = PlugOpenFile(g, tempname, "wb"))) {
if (trace(1))
htrc("%s\n", g->Message);
rc = true;
} else
To_Fbt = PlgGetUser(g)->Openlist;
return rc;
} // end of OpenTempFile
/***********************************************************************/
/* Move intermediate deleted or updated lines. */
/* This works only for file open in binary mode. */
/***********************************************************************/
bool DOSFAM::MoveIntermediateLines(PGLOBAL g, bool *b)
{
int n;
size_t req, len;
for (*b = false, n = Fpos - Spos; n > 0; n -= req) {
if (!UseTemp || !*b)
if (fseek(Stream, Spos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(READ_SEEK_ERROR), strerror(errno));
return true;
} // endif
req = (size_t)MY_MIN(n, Dbflen);
len = fread(DelBuf, 1, req, Stream);
if (trace(1))
htrc("after read req=%d len=%d\n", req, len);
if (len != req) {
snprintf(g->Message, sizeof(g->Message), MSG(DEL_READ_ERROR), (int) req, (int) len);
return true;
} // endif len
if (!UseTemp)
if (fseek(T_Stream, Tpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(WRITE_SEEK_ERR), strerror(errno));
return true;
} // endif
if ((len = fwrite(DelBuf, 1, req, T_Stream)) != req) {
snprintf(g->Message, sizeof(g->Message), MSG(DEL_WRITE_ERROR), strerror(errno));
return true;
} // endif
if (trace(1))
htrc("after write pos=%d\n", ftell(Stream));
Tpos += (int)req;
Spos += (int)req;
if (trace(1))
htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);
*b = true;
} // endfor n
return false;
} // end of MoveIntermediate Lines
/***********************************************************************/
/* Delete the old file and rename the new temp file. */
/* If aborting just delete the new temp file. */
/* If indexed, make the temp file from the arrays. */
/***********************************************************************/
int DOSFAM::RenameTempFile(PGLOBAL g)
{
char *tempname, filetemp[_MAX_PATH], filename[_MAX_PATH];
int rc = RC_OK;
if (To_Fbt)
tempname = (char*)To_Fbt->Fname;
else
return RC_INFO; // Nothing to do ???
// This loop is necessary because, in case of join,
// To_File can have been open several times.
for (PFBLOCK fb = PlgGetUser(g)->Openlist; fb; fb = fb->Next)
if (fb == To_Fb || (fb == To_Fbt))
rc = PlugCloseFile(g, fb);
if (!Abort) {
PlugSetPath(filename, To_File, Tdbp->GetPath());
PlugRemoveType(filetemp, filename);
safe_strcat(filetemp, sizeof(filetemp), ".ttt");
remove(filetemp); // May still be there from previous error
if (rename(filename, filetemp)) { // Save file for security
snprintf(g->Message, sizeof(g->Message), MSG(RENAME_ERROR),
filename, filetemp, strerror(errno));
throw 51;
} else if (rename(tempname, filename)) {
snprintf(g->Message, sizeof(g->Message), MSG(RENAME_ERROR),
tempname, filename, strerror(errno));
rc = rename(filetemp, filename); // Restore saved file
throw 52;
} else if (remove(filetemp)) {
snprintf(g->Message, sizeof(g->Message), MSG(REMOVE_ERROR),
filetemp, strerror(errno));
rc = RC_INFO; // Acceptable
} // endif's
} else
remove(tempname);
return rc;
} // end of RenameTempFile
/***********************************************************************/
/* Table file close routine for DOS access method. */
/***********************************************************************/
void DOSFAM::CloseTableFile(PGLOBAL g, bool abort)
{
int rc;
Abort = abort;
if (UseTemp && T_Stream) {
if (Tdbp->Mode == MODE_UPDATE && !Abort) {
// Copy eventually remaining lines
bool b;
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
Abort = MoveIntermediateLines(g, &b) != RC_OK;
} // endif Abort
// Delete the old file and rename the new temp file.
rc = RenameTempFile(g); // Also close all files
} else {
rc = PlugCloseFile(g, To_Fb);
if (trace(1))
htrc("DOS Close: closing %s rc=%d\n", To_File, rc);
} // endif UseTemp
Stream = NULL; // So we can know whether table is open
T_Stream = NULL;
} // end of CloseTableFile
/***********************************************************************/
/* Rewind routine for DOS access method. */
/***********************************************************************/
void DOSFAM::Rewind(void)
{
if (Stream) // Can be NULL when making index on void table
rewind(Stream);
Rows = 0;
OldBlk = CurBlk = -1;
} // end of Rewind
/* --------------------------- Class BLKFAM -------------------------- */
/***********************************************************************/
/* Constructors. */
/***********************************************************************/
BLKFAM::BLKFAM(PDOSDEF tdp) : DOSFAM(tdp)
{
Blocked = true;
Block = tdp->GetBlock();
Last = tdp->GetLast();
Nrec = tdp->GetElemt();
Closing = false;
BlkPos = tdp->GetTo_Pos();
CurLine = NULL;
NxtLine = NULL;
OutBuf = NULL;
} // end of BLKFAM standard constructor
BLKFAM::BLKFAM(PBLKFAM txfp) : DOSFAM(txfp)
{
Closing = txfp->Closing;
CurLine = txfp->CurLine;
NxtLine = txfp->NxtLine;
OutBuf = txfp->OutBuf;
} // end of BLKFAM copy constructor
/***********************************************************************/
/* Reset: reset position values at the beginning of file. */
/***********************************************************************/
void BLKFAM::Reset(void)
{
DOSFAM::Reset();
Closing = false;
} // end of Reset
/***********************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/***********************************************************************/
int BLKFAM::Cardinality(PGLOBAL g)
{
return (g) ? ((Block > 0) ? (int)((Block - 1) * Nrec + Last) : 0) : 1;
} // end of Cardinality
/***********************************************************************/
/* Use BlockTest to reduce the table estimated size. */
/***********************************************************************/
int BLKFAM::MaxBlkSize(PGLOBAL g, int)
{
int rc = RC_OK, savcur = CurBlk;
int size;
// Roughly estimate the table size as the sum of blocks
// that can contain good rows
for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++)
if ((rc = Tdbp->TestBlock(g)) == RC_OK)
size += (CurBlk == Block - 1) ? Last : Nrec;
else if (rc == RC_EF)
break;
CurBlk = savcur;
return size;
} // end of MaxBlkSize
/***********************************************************************/
/* Allocate the line buffer. For mode Delete or when a temp file is */
/* used another big buffer has to be allocated because is it used */
/* to move or update the lines into the (temp) file. */
/***********************************************************************/
bool BLKFAM::AllocateBuffer(PGLOBAL g)
{
int len;
MODE mode = Tdbp->GetMode();
// For variable length files, Lrecl does not include CRLF
len = Lrecl + ((Tdbp->GetFtype()) ? 0 : Ending);
Buflen = len * Nrec;
CurLine = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
if (UseTemp || mode == MODE_DELETE) {
if (mode == MODE_UPDATE)
OutBuf = (char*)PlugSubAlloc(g, NULL, len + 1);
Dbflen = Buflen;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} else if (mode == MODE_INSERT)
Rbuf = Nrec; // To be used by WriteDB
return false;
} // end of AllocateBuffer
/***********************************************************************/
/* GetRowID: return the RowID of last read record. */
/***********************************************************************/
int BLKFAM::GetRowID(void)
{
return CurNum + Nrec * CurBlk + 1;
} // end of GetRowID
/***********************************************************************/
/* GetPos: return the position of last read record. */
/***********************************************************************/
int BLKFAM::GetPos(void)
{
return (CurNum + Nrec * CurBlk); // Computed file index
} // end of GetPos
/***********************************************************************/
/* GetNextPos: called by DeleteRecords. */
/***********************************************************************/
int BLKFAM::GetNextPos(void)
{
return (int)(Fpos + NxtLine - CurLine);
} // end of GetNextPos
/***********************************************************************/
/* SetPos: Replace the table at the specified position. */
/***********************************************************************/
bool BLKFAM::SetPos(PGLOBAL g, int)
{
snprintf(g->Message, sizeof(g->Message), "Blocked variable tables cannot be used indexed");
return true;
} // end of SetPos
/***********************************************************************/
/* Record file position in case of UPDATE or DELETE. */
/* Not used yet for blocked tables. */
/***********************************************************************/
bool BLKFAM::RecordPos(PGLOBAL)
{
Fpos = (CurNum + Nrec * CurBlk); // Computed file index
return false;
} // end of RecordPos
/***********************************************************************/
/* Skip one record in file. */
/***********************************************************************/
int BLKFAM::SkipRecord(PGLOBAL, bool header)
{
if (header) {
// For Delete
Fpos = BlkPos[0]; // First block starts after the header
if (!UseTemp)
Tpos = Spos = Fpos; // No need to move header
} // endif header
OldBlk = -2; // To force fseek on first block
return RC_OK;
} // end of SkipRecord
/***********************************************************************/
/* ReadBuffer: Read one line for a text file. */
/***********************************************************************/
int BLKFAM::ReadBuffer(PGLOBAL g)
{
int i, rc = RC_OK;
size_t n;
/*********************************************************************/
/* Sequential reading when Placed is not true. */
/*********************************************************************/
if (Placed) {
Placed = false;
} else if (++CurNum < Rbuf) {
CurLine = NxtLine;
// Get the position of the next line in the buffer
while (*NxtLine++ != '\n') ;
// Set caller line buffer
n = NxtLine - CurLine - Ending;
memcpy(Tdbp->GetLine(), CurLine, n);
Tdbp->GetLine()[n] = '\0';
goto fin;
} else if (Rbuf < Nrec && CurBlk != -1) {
return RC_EF;
} else {
/*******************************************************************/
/* New block. */
/*******************************************************************/
CurNum = 0;
next:
if (++CurBlk >= Block)
return RC_EF;
/*******************************************************************/
/* Before reading a new block, check whether block optimization */
/* can be done, as well as for join as for local filtering. */
/*******************************************************************/
switch (Tdbp->TestBlock(g)) {
case RC_EF:
return RC_EF;
case RC_NF:
goto next;
} // endswitch rc
} // endif's
if (OldBlk == CurBlk)
goto ok; // Block is already there
// fseek is required only in non sequential reading
if (CurBlk != OldBlk + 1)
if (fseek(Stream, BlkPos[CurBlk], SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), BlkPos[CurBlk]);
return RC_FX;
} // endif fseek
// Calculate the length of block to read
BlkLen = BlkPos[CurBlk + 1] - BlkPos[CurBlk];
if (trace(1))
htrc("File position is now %d\n", ftell(Stream));
// Read the entire next block
n = fread(To_Buf, 1, (size_t)BlkLen, Stream);
if ((size_t) n == (size_t) BlkLen) {
// ReadBlks++;
num_read++;
Rbuf = (CurBlk == Block - 1) ? Last : Nrec;
ok:
rc = RC_OK;
// Get the position of the current line
for (i = 0, CurLine = To_Buf; i < CurNum; i++)
while (*CurLine++ != '\n') ; // What about Unix ???
// Now get the position of the next line
for (NxtLine = CurLine; *NxtLine++ != '\n';) ;
// Set caller line buffer
n = NxtLine - CurLine - Ending;
memcpy(Tdbp->GetLine(), CurLine, n);
Tdbp->GetLine()[n] = '\0';
} else if (feof(Stream)) {
rc = RC_EF;
} else {
#if defined(_WIN32)
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, _strerror(NULL));
#else
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, strerror(errno));
#endif
if (trace(1))
htrc("%s\n", g->Message);
return RC_FX;
} // endelse
OldBlk = CurBlk; // Last block actually read
IsRead = true; // Is read indeed
fin:
// Store the current record file position for Delete and Update
Fpos = (int)(BlkPos[CurBlk] + CurLine - To_Buf);
return rc;
} // end of ReadBuffer
/***********************************************************************/
/* WriteBuffer: File write routine for the blocked DOS access method. */
/* Update is directly written back into the file, */
/* with this (fast) method, record size cannot change. */
/***********************************************************************/
int BLKFAM::WriteBuffer(PGLOBAL g)
{
if (Tdbp->GetMode() == MODE_INSERT) {
/*******************************************************************/
/* In Insert mode, blocks are added sequentially to the file end. */
/*******************************************************************/
if (!Closing) { // Add line to the write buffer
strcat(strcpy(CurLine, Tdbp->GetLine()), CrLf);
if (++CurNum != Rbuf) {
CurLine += strlen(CurLine);
return RC_OK; // We write only full blocks
} // endif CurNum
} // endif Closing
// Now start the writing process.
NxtLine = CurLine + strlen(CurLine);
BlkLen = (int)(NxtLine - To_Buf);
if (fwrite(To_Buf, 1, BlkLen, Stream) != (size_t)BlkLen) {
snprintf(g->Message, sizeof(g->Message), MSG(FWRITE_ERROR), strerror(errno));
Closing = true; // To tell CloseDB about a Write error
return RC_FX;
} // endif size
CurBlk++;
CurNum = 0;
CurLine = To_Buf;
} else {
/*******************************************************************/
/* Mode == MODE_UPDATE. */
/*******************************************************************/
const char *crlf;
size_t len;
int curpos = ftell(Stream);
bool moved = true;
// T_Stream is the temporary stream or the table file stream itself
if (!T_Stream)
{
if (UseTemp /*&& Tdbp->GetMode() == MODE_UPDATE*/) {
if (OpenTempFile(g))
return RC_FX;
} else
T_Stream = Stream;
}
if (UseTemp) {
/*****************************************************************/
/* We are using a temporary file. Before writing the updated */
/* record, we must eventually copy all the intermediate records */
/* that have not been updated. */
/*****************************************************************/
if (MoveIntermediateLines(g, &moved))
return RC_FX;
Spos = GetNextPos(); // New start position
// Prepare the output buffer
#if defined(_WIN32)
crlf = "\r\n";
#else
crlf = "\n";
#endif // _WIN32
strcat(strcpy(OutBuf, Tdbp->GetLine()), crlf);
len = strlen(OutBuf);
} else {
if (fseek(Stream, Fpos, SEEK_SET)) { // Fpos is last position
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0);
return RC_FX;
} // endif fseek
// Replace the line inside read buffer (length has not changed)
memcpy(CurLine, Tdbp->GetLine(), strlen(Tdbp->GetLine()));
OutBuf = CurLine;
len = (size_t)(NxtLine - CurLine);
} // endif UseTemp
if (fwrite(OutBuf, 1, len, T_Stream) != (size_t)len) {
snprintf(g->Message, sizeof(g->Message), MSG(FWRITE_ERROR), strerror(errno));
return RC_FX;
} // endif fwrite
if (moved)
if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSEEK_ERROR), strerror(errno));
return RC_FX;
} // endif
} // endif Mode
return RC_OK;
} // end of WriteBuffer
/***********************************************************************/
/* Table file close routine for DOS access method. */
/***********************************************************************/
void BLKFAM::CloseTableFile(PGLOBAL g, bool abort)
{
int rc, wrc = RC_OK;
Abort = abort;
if (UseTemp && T_Stream) {
if (Tdbp->GetMode() == MODE_UPDATE && !Abort) {
// Copy eventually remaining lines
bool b;
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
Abort = MoveIntermediateLines(g, &b) != RC_OK;
} // endif Abort
// Delete the old file and rename the new temp file.
rc = RenameTempFile(g); // Also close all files
} else {
// Closing is True if last Write was in error
if (Tdbp->GetMode() == MODE_INSERT && CurNum && !Closing) {
// Some more inserted lines remain to be written
Rbuf = CurNum--;
Closing = true;
wrc = WriteBuffer(g);
} else if (Modif && !Closing) {
// Last updated block remains to be written
Closing = true;
wrc = ReadBuffer(g);
} // endif's
rc = PlugCloseFile(g, To_Fb);
if (trace(1))
htrc("BLK CloseTableFile: closing %s mode=%d wrc=%d rc=%d\n",
To_File, Tdbp->GetMode(), wrc, rc);
} // endif UseTemp
Stream = NULL; // So we can know whether table is open
} // end of CloseTableFile
/***********************************************************************/
/* Rewind routine for DOS access method. */
/* Note: commenting out OldBlk = -1 has two advantages: */
/* 1 - It forces fseek on first block, thus suppressing the need to */
/* rewind the file, anyway unuseful when second pass if indexed. */
/* 2 - It permit to avoid re-reading small tables having only 1 block.*/
/***********************************************************************/
void BLKFAM::Rewind(void)
{
//rewind(Stream); will be placed by fseek
CurBlk = -1;
CurNum = Rbuf;
//OldBlk = -1; commented out in case we reuse last read block
//Rbuf = 0; commented out in case we reuse last read block
} // end of Rewind
/* --------------------------- Class BINFAM -------------------------- */
#if 0
/***********************************************************************/
/* BIN GetFileLength: returns file size in number of bytes. */
/***********************************************************************/
int BINFAM::GetFileLength(PGLOBAL g)
{
int len;
if (!Stream)
len = TXTFAM::GetFileLength(g);
else
if ((len = _filelength(_fileno(Stream))) < 0)
snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", To_File);
xtrc(1, "File length=%d\n", len);
return len;
} // end of GetFileLength
/***********************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/***********************************************************************/
int BINFAM::Cardinality(PGLOBAL g)
{
return (g) ? -1 : 0;
} // end of Cardinality
/***********************************************************************/
/* OpenTableFile: Open a DOS/UNIX table file using C standard I/Os. */
/***********************************************************************/
bool BINFAM::OpenTableFile(PGLOBAL g) {
char opmode[4], filename[_MAX_PATH];
MODE mode = Tdbp->GetMode();
PDBUSER dbuserp = PlgGetUser(g);
switch (mode) {
case MODE_READ:
snprintf(opmode, sizeof(opmode), "rb");
break;
case MODE_WRITE:
snprintf(opmode, sizeof(opmode), "wb");
break;
default:
snprintf(g->Message, sizeof(g->Message), MSG(BAD_OPEN_MODE), mode);
return true;
} // endswitch Mode
// Now open the file stream
PlugSetPath(filename, To_File, Tdbp->GetPath());
if (!(Stream = PlugOpenFile(g, filename, opmode))) {
if (trace(1))
htrc("%s\n", g->Message);
return (mode == MODE_READ && errno == ENOENT)
? PushWarning(g, Tdbp) : true;
} // endif Stream
if (trace(1))
htrc("File %s open Stream=%p mode=%s\n", filename, Stream, opmode);
To_Fb = dbuserp->Openlist; // Keep track of File block
/*********************************************************************/
/* Allocate the line buffer. */
/*********************************************************************/
return AllocateBuffer(g);
} // end of OpenTableFile
#endif // 0
/***********************************************************************/
/* Allocate the line buffer. For mode Delete a bigger buffer has to */
/* be allocated because is it also used to move lines into the file. */
/***********************************************************************/
bool BINFAM::AllocateBuffer(PGLOBAL g)
{
MODE mode = Tdbp->GetMode();
// Lrecl is Ok
Buflen = Lrecl;
// Buffer will be allocated separately
if (mode == MODE_ANY) {
xtrc(1, "SubAllocating a buffer of %d bytes\n", Buflen);
To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
} else if (UseTemp || mode == MODE_DELETE) {
// Have a big buffer to move lines
Dbflen = Buflen * DOS_BUFF_LEN;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} // endif mode
return false;
#if 0
MODE mode = Tdbp->GetMode();
// Lrecl is Ok
Dbflen = Buflen = Lrecl;
if (trace(1))
htrc("SubAllocating a buffer of %d bytes\n", Buflen);
DelBuf = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
return false;
#endif // 0
} // end of AllocateBuffer
#if 0
/***********************************************************************/
/* GetRowID: return the RowID of last read record. */
/***********************************************************************/
int BINFAM::GetRowID(void) {
return Rows;
} // end of GetRowID
/***********************************************************************/
/* GetPos: return the position of last read record. */
/***********************************************************************/
int BINFAM::GetPos(void) {
return Fpos;
} // end of GetPos
/***********************************************************************/
/* GetNextPos: return the position of next record. */
/***********************************************************************/
int BINFAM::GetNextPos(void) {
return ftell(Stream);
} // end of GetNextPos
/***********************************************************************/
/* SetPos: Replace the table at the specified position. */
/***********************************************************************/
bool BINFAM::SetPos(PGLOBAL g, int pos) {
Fpos = pos;
if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), Fpos);
return true;
} // endif
Placed = true;
return false;
} // end of SetPos
/***********************************************************************/
/* Record file position in case of UPDATE or DELETE. */
/***********************************************************************/
bool BINFAM::RecordPos(PGLOBAL g) {
if ((Fpos = ftell(Stream)) < 0) {
snprintf(g->Message, sizeof(g->Message), MSG(FTELL_ERROR), 0, strerror(errno));
// strcat(g->Message, " (possible wrong ENDING option value)");
return true;
} // endif Fpos
return false;
} // end of RecordPos
#endif // 0
/***********************************************************************/
/* ReadBuffer: Read one line for a text file. */
/***********************************************************************/
int BINFAM::ReadBuffer(PGLOBAL g)
{
int rc;
if (!Stream)
return RC_EF;
xtrc(2, "ReadBuffer: Tdbp=%p To_Line=%p Placed=%d\n",
Tdbp, Tdbp->GetLine(), Placed);
if (!Placed) {
/*******************************************************************/
/* Record file position in case of UPDATE or DELETE. */
/*******************************************************************/
if (RecordPos(g))
return RC_FX;
CurBlk = (int)Rows++;
xtrc(2, "ReadBuffer: CurBlk=%d\n", CurBlk);
} else
Placed = false;
xtrc(2, " About to read: bstream=%p To_Buf=%p Buflen=%d Fpos=%d\n",
Stream, To_Buf, Buflen, Fpos);
// Read the prefix giving the row length
if (!fread(&Recsize, sizeof(size_t), 1, Stream)) {
if (!feof(Stream)) {
snprintf(g->Message, sizeof(g->Message), "Error reading line prefix\n");
return RC_FX;
} else
return RC_EF;
} else if (Recsize > (unsigned)Buflen) {
snprintf(g->Message, sizeof(g->Message), "Record too big (Recsize=%zd Buflen=%d)\n", Recsize, Buflen);
return RC_FX;
} // endif Recsize
if (fread(To_Buf, Recsize, 1, Stream)) {
xtrc(2, " Read: To_Buf=%p Recsize=%zd\n", To_Buf, Recsize);
num_read++;
rc = RC_OK;
} else if (feof(Stream)) {
rc = RC_EF;
} else {
#if defined(_WIN32)
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, _strerror(NULL));
#else
snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), To_File, strerror(0));
#endif
xtrc(2, "%s\n", g->Message);
rc = RC_FX;
} // endif's fread
xtrc(2, "ReadBuffer: rc=%d\n", rc);
IsRead = true;
return rc;
} // end of ReadBuffer
/***********************************************************************/
/* WriteBuffer: File write routine for BIN access method. */
/***********************************************************************/
int BINFAM::WriteBuffer(PGLOBAL g)
{
int curpos = 0;
bool moved = true;
// T_Stream is the temporary stream or the table file stream itself
if (!T_Stream) {
if (UseTemp && Tdbp->GetMode() == MODE_UPDATE) {
if (OpenTempFile(g))
return RC_FX;
} else
T_Stream = Stream;
} // endif T_Stream
if (Tdbp->GetMode() == MODE_UPDATE) {
/*******************************************************************/
/* Here we simply rewrite a record on itself. There are two cases */
/* were another method should be used, a/ when Update apply to */
/* the whole file, b/ when updating the last field of a variable */
/* length file. The method could be to rewrite a new file, then */
/* to erase the old one and rename the new updated file. */
/*******************************************************************/
curpos = ftell(Stream);
if (trace(1))
htrc("Last : %d cur: %d\n", Fpos, curpos);
if (UseTemp) {
/*****************************************************************/
/* We are using a temporary file. */
/* Before writing the updated record, we must eventually copy */
/* all the intermediate records that have not been updated. */
/*****************************************************************/
if (MoveIntermediateLines(g, &moved))
return RC_FX;
Spos = curpos; // New start position
} else
// Update is directly written back into the file,
// with this (fast) method, record size cannot change.
if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0);
return RC_FX;
} // endif
} // endif mode
/*********************************************************************/
/* Prepare writing the line. */
/*********************************************************************/
//memcpy(To_Buf, Tdbp->GetLine(), Recsize);
/*********************************************************************/
/* Now start the writing process. */
/*********************************************************************/
if (fwrite(&Recsize, sizeof(size_t), 1, T_Stream) != 1) {
snprintf(g->Message, sizeof(g->Message), "Error %d writing prefix to %s",
errno, To_File);
return RC_FX;
} else if (fwrite(To_Buf, Recsize, 1, T_Stream) != 1) {
snprintf(g->Message, sizeof(g->Message), "Error %d writing %zd bytes to %s",
errno, Recsize, To_File);
return RC_FX;
} // endif fwrite
if (Tdbp->GetMode() == MODE_UPDATE && moved)
if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSEEK_ERROR), strerror(errno));
return RC_FX;
} // endif
xtrc(1, "Binary write done\n");
return RC_OK;
} // end of WriteBuffer
#if 0
/***********************************************************************/
/* Data Base delete line routine for DOS and BLK access methods. */
/***********************************************************************/
int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
{
bool moved;
int curpos = ftell(Stream);
/*********************************************************************/
/* There is an alternative here: */
/* 1 - use a temporary file in which are copied all not deleted */
/* lines, at the end the original file will be deleted and */
/* the temporary file renamed to the original file name. */
/* 2 - directly move the not deleted lines inside the original */
/* file, and at the end erase all trailing records. */
/* This will be experimented. */
/*********************************************************************/
if (trace(1))
htrc(
"DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
irc, UseTemp, curpos, Fpos, Tpos, Spos);
if (irc != RC_OK) {
/*******************************************************************/
/* EOF: position Fpos at the end-of-file position. */
/*******************************************************************/
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
if (trace(1))
htrc("Fpos placed at file end=%d\n", Fpos);
} // endif irc
if (Tpos == Spos) {
/*******************************************************************/
/* First line to delete, Open temporary file. */
/*******************************************************************/
if (UseTemp) {
if (OpenTempFile(g))
return RC_FX;
} else {
/*****************************************************************/
/* Move of eventual preceding lines is not required here. */
/* Set the target file as being the source file itself. */
/* Set the future Tpos, and give Spos a value to block copying. */
/*****************************************************************/
T_Stream = Stream;
Spos = Tpos = Fpos;
} // endif UseTemp
} // endif Tpos == Spos
/*********************************************************************/
/* Move any intermediate lines. */
/*********************************************************************/
if (MoveIntermediateLines(g, &moved))
return RC_FX;
if (irc == RC_OK) {
/*******************************************************************/
/* Reposition the file pointer and set Spos. */
/*******************************************************************/
if (!UseTemp || moved)
if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0);
return RC_FX;
} // endif
Spos = GetNextPos(); // New start position
if (trace(1))
htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
} else {
/*******************************************************************/
/* Last call after EOF has been reached. */
/* The UseTemp case is treated in CloseTableFile. */
/*******************************************************************/
if (!UseTemp & !Abort) {
/*****************************************************************/
/* Because the chsize functionality is only accessible with a */
/* system call we must close the file and reopen it with the */
/* open function (_fopen for MS ??) this is still to be checked */
/* for compatibility with Text files and other OS's. */
/*****************************************************************/
char filename[_MAX_PATH];
int h; // File handle, return code
PlugSetPath(filename, To_File, Tdbp->GetPath());
/*rc=*/ PlugCloseFile(g, To_Fb);
if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
return RC_FX;
/*****************************************************************/
/* Remove extra records. */
/*****************************************************************/
#if defined(_WIN32)
if (chsize(h, Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(CHSIZE_ERROR), strerror(errno));
close(h);
return RC_FX;
} // endif
#else
if (ftruncate(h, (off_t)Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(TRUNCATE_ERROR), strerror(errno));
close(h);
return RC_FX;
} // endif
#endif
close(h);
if (trace(1))
htrc("done, h=%d irc=%d\n", h, irc);
} // endif !UseTemp
} // endif irc
return RC_OK; // All is correct
} // end of DeleteRecords
/***********************************************************************/
/* Table file close routine for DOS access method. */
/***********************************************************************/
void BINFAM::CloseTableFile(PGLOBAL g, bool abort)
{
int rc;
Abort = abort;
rc = PlugCloseFile(g, To_Fb);
xtrc(1, "BIN Close: closing %s rc=%d\n", To_File, rc);
Stream = NULL; // So we can know whether table is open
} // end of CloseTableFile
/***********************************************************************/
/* Rewind routine for BIN access method. */
/***********************************************************************/
void BINFAM::Rewind(void)
{
if (Stream) // Can be NULL when making index on void table
rewind(Stream);
Rows = 0;
OldBlk = CurBlk = -1;
} // end of Rewind
#endif // 0