mirror of
https://github.com/MariaDB/server.git
synced 2025-01-27 09:14:17 +01:00
2ff01e763e
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.
2123 lines
70 KiB
C++
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
|