1 /*********** File AM Txt C++ Program Source Code File (.CPP) ***********/
2 /* PROGRAM NAME: FILAMTXT                                              */
3 /* -------------                                                       */
4 /*  Version 1.8                                                        */
5 /*                                                                     */
6 /* COPYRIGHT:                                                          */
7 /* ----------                                                          */
8 /*  (C) Copyright to the author Olivier BERTRAND          2005-2020    */
9 /*                                                                     */
10 /* WHAT THIS PROGRAM DOES:                                             */
11 /* -----------------------                                             */
12 /*  This program are the Text file access method classes.              */
13 /*                                                                     */
14 /***********************************************************************/
15 
16 /***********************************************************************/
17 /*  Include relevant sections of the System header files.              */
18 /***********************************************************************/
19 #include "my_global.h"
20 #if defined(_WIN32)
21 #include <io.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #if defined(__BORLANDC__)
25 #define __MFC_COMPAT__                   // To define min/max as macro
26 #endif   // __BORLANDC__
27 //#include <windows.h>
28 #else   // !_WIN32
29 #if defined(UNIX) || defined(UNIV_LINUX)
30 #include <errno.h>
31 #include <unistd.h>
32 //#if !defined(sun)                      // Sun has the ftruncate fnc.
33 //#define USETEMP                        // Force copy mode for DELETE
34 //#endif   // !sun
35 #else   // !UNIX
36 #include <io.h>
37 #endif  // !UNIX
38 #include <fcntl.h>
39 #endif  // !_WIN32
40 
41 /***********************************************************************/
42 /*  Include application header files:                                  */
43 /*  global.h    is header containing all global declarations.          */
44 /*  plgdbsem.h  is header containing the DB application declarations.  */
45 /*  filamtxt.h  is header containing the file AM classes declarations. */
46 /***********************************************************************/
47 #include "global.h"
48 #include "plgdbsem.h"
49 #include "filamtxt.h"
50 #include "tabdos.h"
51 #include "tabjson.h"
52 
53 #if defined(UNIX) || defined(UNIV_LINUX)
54 #include "osutil.h"
55 #define _fileno fileno
56 #define _O_RDONLY O_RDONLY
57 #endif
58 
59 extern int num_read, num_there, num_eq[2];               // Statistics
60 
61 /***********************************************************************/
62 /*  Routine called externally by TXTFAM SortedRows functions.          */
63 /***********************************************************************/
64 PARRAY MakeValueArray(PGLOBAL g, PPARM pp);
65 
66 /* --------------------------- Class TXTFAM -------------------------- */
67 
68 /***********************************************************************/
69 /*  Constructors.                                                      */
70 /***********************************************************************/
TXTFAM(PDOSDEF tdp)71 TXTFAM::TXTFAM(PDOSDEF tdp)
72   {
73   Tdbp = NULL;
74   To_Fb = NULL;
75 
76 	if (tdp) {
77 		To_File = tdp->Fn;
78 		Lrecl = tdp->Lrecl;
79 		Eof = tdp->Eof;
80 		Ending = tdp->Ending;
81 	} else {
82 		To_File = NULL;
83 		Lrecl = 0;
84 		Eof = false;
85 #if defined(_WIN32)
86 		Ending = 2;
87 #else
88 		Ending = 1;
89 #endif
90 	}	// endif tdp
91 
92   Placed = false;
93   IsRead = true;
94   Blocked = false;
95   To_Buf = NULL;
96   DelBuf = NULL;
97   BlkPos = NULL;
98   To_Pos = NULL;
99   To_Sos = NULL;
100   To_Upd = NULL;
101   Posar = NULL;
102   Sosar = NULL;
103   Updar = NULL;
104   BlkLen = 0;
105   Buflen = 0;
106   Dbflen = 0;
107   Rows = 0;
108   DelRows = 0;
109   Headlen = 0;
110   Block = 0;
111   Last = 0;
112   Nrec = 1;
113   OldBlk = -1;
114   CurBlk = -1;
115   ReadBlks = 0;
116   CurNum = 0;
117   Rbuf = 0;
118   Modif = 0;
119   Blksize = 0;
120   Fpos = Spos = Tpos = 0;
121   Padded = false;
122   Abort = false;
123   CrLf = (char*)(Ending == 1 ? "\n" : "\r\n");
124   } // end of TXTFAM standard constructor
125 
TXTFAM(PTXF txfp)126 TXTFAM::TXTFAM(PTXF txfp)
127   {
128   Tdbp = txfp->Tdbp;
129   To_Fb = txfp->To_Fb;
130   To_File = txfp->To_File;
131   Lrecl = txfp->Lrecl;
132   Placed = txfp->Placed;
133   IsRead = txfp->IsRead;
134   Blocked = txfp->Blocked;
135   To_Buf = txfp->To_Buf;
136   DelBuf = txfp->DelBuf;
137   BlkPos = txfp->BlkPos;
138   To_Pos = txfp->To_Pos;
139   To_Sos = txfp->To_Sos;
140   To_Upd = txfp->To_Upd;
141   Posar = txfp->Posar;
142   Sosar = txfp->Sosar;
143   Updar = txfp->Updar;
144   BlkLen = txfp->BlkLen;
145   Buflen = txfp->Buflen;
146   Dbflen = txfp->Dbflen;
147   Rows = txfp->Rows;
148   DelRows = txfp->DelRows;
149   Headlen = txfp->Headlen;
150   Block = txfp->Block;
151   Last = txfp->Last;
152   Nrec = txfp->Nrec;
153   OldBlk = txfp->OldBlk;
154   CurBlk = txfp->CurBlk;
155   ReadBlks = txfp->ReadBlks;
156   CurNum = txfp->CurNum;
157   Rbuf = txfp->Rbuf;
158   Modif = txfp->Modif;
159   Blksize = txfp->Blksize;
160   Fpos = txfp->Fpos;
161   Spos = txfp->Spos;
162   Tpos = txfp->Tpos;
163   Padded = txfp->Padded;
164   Eof = txfp->Eof;
165   Ending = txfp->Ending;
166   Abort = txfp->Abort;
167   CrLf = txfp->CrLf;
168   } // end of TXTFAM copy constructor
169 
170 /***********************************************************************/
171 /*  Reset: reset position values at the beginning of file.             */
172 /***********************************************************************/
Reset(void)173 void TXTFAM::Reset(void)
174   {
175   Rows = 0;
176   DelRows = 0;
177   OldBlk = -1;
178   CurBlk = -1;
179   ReadBlks = 0;
180   CurNum = 0;
181   Rbuf = 0;
182   Modif = 0;
183   Placed = false;
184   } // end of Reset
185 
186 /***********************************************************************/
187 /*  TXT GetFileLength: returns file size in number of bytes.           */
188 /***********************************************************************/
GetFileLength(PGLOBAL g)189 int TXTFAM::GetFileLength(PGLOBAL g)
190   {
191   char filename[_MAX_PATH];
192   int  h;
193   int  len;
194 
195   PlugSetPath(filename, To_File, Tdbp->GetPath());
196   h= global_open(g, MSGID_OPEN_MODE_STRERROR, filename, _O_RDONLY);
197 
198   if (trace(1))
199     htrc("GetFileLength: fn=%s h=%d\n", filename, h);
200 
201   if (h == -1) {
202     if (errno != ENOENT) {
203       if (trace(1))
204         htrc("%s\n", g->Message);
205 
206       len = -1;
207     } else {
208       len = 0;          // File does not exist yet
209       g->Message[0]= '\0';
210     } // endif errno
211 
212   } else {
213     if ((len = _filelength(h)) < 0)
214       sprintf(g->Message, MSG(FILELEN_ERROR), "_filelength", filename);
215 
216     if (Eof && len)
217       len--;              // Do not count the EOF character
218 
219     close(h);
220   } // endif h
221 
222   return len;
223   } // end of GetFileLength
224 
225 /***********************************************************************/
226 /*  Cardinality: returns table cardinality in number of rows.          */
227 /*  This function can be called with a null argument to test the       */
228 /*  availability of Cardinality implementation (1 yes, 0 no).          */
229 /*  Note: This function is meant only for fixed length files but is    */
230 /*  placed here to be available to FIXFAM and MPXFAM classes.          */
231 /***********************************************************************/
Cardinality(PGLOBAL g)232 int TXTFAM::Cardinality(PGLOBAL g)
233   {
234   if (g) {
235     int card = -1;
236     int len = GetFileLength(g);
237 
238     if (len >= 0) {
239       if (Padded && Blksize) {
240         if (!(len % Blksize))
241           card = (len / Blksize) * Nrec;
242         else
243           sprintf(g->Message, MSG(NOT_FIXED_LEN), To_File, len, Lrecl);
244 
245       } else {
246         if (!(len % Lrecl))
247           card = len / (int)Lrecl;           // Fixed length file
248         else
249           sprintf(g->Message, MSG(NOT_FIXED_LEN), To_File, len, Lrecl);
250 
251       } // endif Padded
252 
253       if (trace(1))
254         htrc(" Computed max_K=%d Filen=%d lrecl=%d\n",
255               card, len, Lrecl);
256 
257     } else
258       card = 0;
259 
260     // Set number of blocks for later use
261     Block = (card > 0) ? (card + Nrec - 1) / Nrec : 0;
262     return card;
263   } else
264     return 1;
265 
266   } // end of Cardinality
267 
268 /***********************************************************************/
269 /*  Use BlockTest to reduce the table estimated size.                  */
270 /*  Note: This function is meant only for fixed length files but is    */
271 /*  placed here to be available to FIXFAM and MPXFAM classes.          */
272 /***********************************************************************/
MaxBlkSize(PGLOBAL g,int s)273 int TXTFAM::MaxBlkSize(PGLOBAL g, int s)
274   {
275   int rc = RC_OK, savcur = CurBlk, blm1 = Block - 1;
276   int size, last = s - blm1 * Nrec;
277 
278   // Roughly estimate the table size as the sum of blocks
279   // that can contain good rows
280   for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++)
281     if ((rc = Tdbp->TestBlock(g)) == RC_OK)
282       size += (CurBlk == blm1) ? last : Nrec;
283     else if (rc == RC_EF)
284       break;
285 
286   CurBlk = savcur;
287   return size;
288   } // end of MaxBlkSize
289 
290 /***********************************************************************/
291 /*  AddListValue: Used when doing indexed update or delete.            */
292 /***********************************************************************/
AddListValue(PGLOBAL g,int type,void * val,PPARM * top)293 bool TXTFAM::AddListValue(PGLOBAL g, int type, void *val, PPARM *top)
294   {
295   PPARM pp = (PPARM)PlugSubAlloc(g, NULL, sizeof(PARM));
296 
297   switch (type) {
298 //  case TYPE_INT:
299 //    pp->Value = PlugSubAlloc(g, NULL, sizeof(int));
300 //    *((int*)pp->Value) = *((int*)val);
301 //    break;
302     case TYPE_VOID:
303       pp->Intval = *(int*)val;
304       break;
305 //  case TYPE_STRING:
306 //    pp->Value = PlugDup(g, (char*)val);
307 //    break;
308     case TYPE_PCHAR:
309       pp->Value = val;
310       break;
311     default:
312       return true;
313   } // endswitch type
314 
315   pp->Type = type;
316   pp->Domain = 0;
317   pp->Next = *top;
318   *top = pp;
319   return false;
320   } // end of AddListValue
321 
322 /***********************************************************************/
323 /*  Store needed values for indexed UPDATE or DELETE.                  */
324 /***********************************************************************/
StoreValues(PGLOBAL g,bool upd)325 int TXTFAM::StoreValues(PGLOBAL g, bool upd)
326 {
327   int  pos = GetPos();
328   bool rc = AddListValue(g, TYPE_VOID, &pos, &To_Pos);
329 
330   if (!rc) {
331     pos = GetNextPos();
332     rc = AddListValue(g, TYPE_VOID, &pos, &To_Sos);
333     } // endif rc
334 
335   if (upd && !rc) {
336     char *buf;
337 
338     if (Tdbp->PrepareWriting(g))
339       return RC_FX;
340 
341     buf = PlugDup(g, Tdbp->GetLine());
342     rc = AddListValue(g, TYPE_PCHAR, buf, &To_Upd);
343     } // endif upd
344 
345   return rc ? RC_FX : RC_OK;
346 } // end of StoreValues
347 
348 /***********************************************************************/
349 /*  UpdateSortedRows. When updating using indexing, the issue is that  */
350 /*  record are not necessarily updated in sequential order.            */
351 /*  Moving intermediate lines cannot be done while making them because */
352 /*  this can cause extra wrong records to be included in the new file. */
353 /*  What we do here is to reorder the updated records and do all the   */
354 /*  updates ordered by record position.                                */
355 /***********************************************************************/
UpdateSortedRows(PGLOBAL g)356 int TXTFAM::UpdateSortedRows(PGLOBAL g)
357   {
358   int  *ix, i;
359 
360   /*********************************************************************/
361   /*  Get the stored update values and sort them.                      */
362   /*********************************************************************/
363   if (!(Posar = MakeValueArray(g, To_Pos))) {
364 //  strcpy(g->Message, "Position array is null");
365 //  return RC_INFO;
366     return RC_OK;         // Nothing to do
367   } else if (!(Sosar = MakeValueArray(g, To_Sos))) {
368     strcpy(g->Message, "Start position array is null");
369     goto err;
370   } else if (!(Updar = MakeValueArray(g, To_Upd))) {
371     strcpy(g->Message, "Updated line array is null");
372     goto err;
373   } else if (!(ix = (int*)Posar->GetSortIndex(g))) {
374     strcpy(g->Message, "Error getting array sort index");
375     goto err;
376   } // endif's
377 
378   Rewind();
379 
380   for (i = 0; i < Posar->GetNval(); i++) {
381     SetPos(g, Sosar->GetIntValue(ix[i]));
382     Fpos = Posar->GetIntValue(ix[i]);
383     strcpy(Tdbp->To_Line, Updar->GetStringValue(ix[i]));
384 
385     // Now write the updated line.
386     if (WriteBuffer(g))
387       goto err;
388 
389     } // endfor i
390 
391   return RC_OK;
392 
393 err:
394   if (trace(1))
395     htrc("%s\n", g->Message);
396 
397   return RC_FX;
398   } // end of UpdateSortedRows
399 
400 /***********************************************************************/
401 /*  DeleteSortedRows. When deleting using indexing, the issue is that  */
402 /*  record are not necessarily deleted in sequential order. Moving     */
403 /*  intermediate lines cannot be done while deleing them because       */
404 /*  this can cause extra wrong records to be included in the new file. */
405 /*  What we do here is to reorder the deleted record and delete from   */
406 /*  the file from the ordered deleted records.                         */
407 /***********************************************************************/
DeleteSortedRows(PGLOBAL g)408 int TXTFAM::DeleteSortedRows(PGLOBAL g)
409   {
410   int  *ix, i, irc;
411 
412   /*********************************************************************/
413   /*  Get the stored delete values and sort them.                      */
414   /*********************************************************************/
415   if (!(Posar = MakeValueArray(g, To_Pos))) {
416 //  strcpy(g->Message, "Position array is null");
417 //  return RC_INFO;
418     return RC_OK;             // Nothing to do
419   } else if (!(Sosar = MakeValueArray(g, To_Sos))) {
420     strcpy(g->Message, "Start position array is null");
421     goto err;
422   } else if (!(ix = (int*)Posar->GetSortIndex(g))) {
423     strcpy(g->Message, "Error getting array sort index");
424     goto err;
425   } // endif's
426 
427   Tpos = Spos = 0;
428 
429   for (i = 0; i < Posar->GetNval(); i++) {
430     if ((irc = InitDelete(g, Posar->GetIntValue(ix[i]),
431                              Sosar->GetIntValue(ix[i]))) == RC_FX)
432       goto err;
433 
434     // Now delete the sorted rows
435     if (DeleteRecords(g, irc))
436       goto err;
437 
438     } // endfor i
439 
440   return RC_OK;
441 
442 err:
443   if (trace(1))
444     htrc("%s\n", g->Message);
445 
446   return RC_FX;
447   } // end of DeleteSortedRows
448 
449 /***********************************************************************/
450 /*  The purpose of this function is to deal with access methods that   */
451 /*  are not coherent regarding the use of SetPos and GetPos.           */
452 /***********************************************************************/
InitDelete(PGLOBAL g,int,int)453 int TXTFAM::InitDelete(PGLOBAL g, int, int)
454   {
455   strcpy(g->Message, "InitDelete should not be used by this table type");
456   return RC_FX;
457   } // end of InitDelete
458 
459 /* --------------------------- Class DOSFAM -------------------------- */
460 
461 /***********************************************************************/
462 /*  Constructors.                                                      */
463 /***********************************************************************/
DOSFAM(PDOSDEF tdp)464 DOSFAM::DOSFAM(PDOSDEF tdp) : TXTFAM(tdp)
465   {
466   To_Fbt = NULL;
467   Stream = NULL;
468   T_Stream = NULL;
469   UseTemp = false;
470   Bin = false;
471   } // end of DOSFAM standard constructor
472 
DOSFAM(PDOSFAM tdfp)473 DOSFAM::DOSFAM(PDOSFAM tdfp) : TXTFAM(tdfp)
474   {
475   To_Fbt = tdfp->To_Fbt;
476   Stream = tdfp->Stream;
477   T_Stream = tdfp->T_Stream;
478   UseTemp = tdfp->UseTemp;
479   Bin = tdfp->Bin;
480   } // end of DOSFAM copy constructor
481 
DOSFAM(PBLKFAM tdfp,PDOSDEF tdp)482 DOSFAM::DOSFAM(PBLKFAM tdfp, PDOSDEF tdp) : TXTFAM(tdp)
483   {
484   Tdbp = tdfp->Tdbp;
485   To_Fb = tdfp->To_Fb;
486   To_Fbt = tdfp->To_Fbt;
487   Stream = tdfp->Stream;
488   T_Stream = tdfp->T_Stream;
489   UseTemp = tdfp->UseTemp;
490   Bin = tdfp->Bin;
491   } // end of DOSFAM constructor from BLKFAM
492 
493 /***********************************************************************/
494 /*  Reset: reset position values at the beginning of file.             */
495 /***********************************************************************/
Reset(void)496 void DOSFAM::Reset(void)
497   {
498   TXTFAM::Reset();
499   Bin = false;
500   Fpos = Tpos = Spos = 0;
501   } // end of Reset
502 
503 /***********************************************************************/
504 /*  DOS GetFileLength: returns file size in number of bytes.           */
505 /***********************************************************************/
GetFileLength(PGLOBAL g)506 int DOSFAM::GetFileLength(PGLOBAL g)
507   {
508   int len;
509 
510   if (!Stream)
511     len = TXTFAM::GetFileLength(g);
512   else
513     if ((len = _filelength(_fileno(Stream))) < 0)
514       sprintf(g->Message, MSG(FILELEN_ERROR), "_filelength", To_File);
515 
516   if (trace(1))
517     htrc("File length=%d\n", len);
518 
519   return len;
520   } // end of GetFileLength
521 
522 /***********************************************************************/
523 /*  Cardinality: returns table cardinality in number of rows.          */
524 /*  This function can be called with a null argument to test the       */
525 /*  availability of Cardinality implementation (1 yes, 0 no).          */
526 /***********************************************************************/
Cardinality(PGLOBAL g)527 int DOSFAM::Cardinality(PGLOBAL g)
528   {
529   return (g) ? -1 : 0;
530   } // end of Cardinality
531 
532 /***********************************************************************/
533 /*  Use BlockTest to reduce the table estimated size.                  */
534 /*  Note: This function is not really implemented yet.                 */
535 /***********************************************************************/
MaxBlkSize(PGLOBAL,int s)536 int DOSFAM::MaxBlkSize(PGLOBAL, int s)
537   {
538   return s;
539   } // end of MaxBlkSize
540 
541 /***********************************************************************/
542 /*  OpenTableFile: Open a DOS/UNIX table file using C standard I/Os.   */
543 /***********************************************************************/
OpenTableFile(PGLOBAL g)544 bool DOSFAM::OpenTableFile(PGLOBAL g)
545   {
546   char    opmode[4], filename[_MAX_PATH];
547 //int     ftype = Tdbp->GetFtype();
548   MODE    mode = Tdbp->Mode;
549   PDBUSER dbuserp = PlgGetUser(g);
550 
551   // This is required when using Unix files under Windows and vice versa
552 //Bin = (Blocked || Ending != CRLF);
553   Bin = true;             // To avoid ftell problems
554 
555   switch (mode) {
556     case MODE_READ:
557       strcpy(opmode, "r");
558       break;
559     case MODE_DELETE:
560       if (!Tdbp->Next) {
561         // Store the number of deleted lines
562         DelRows = Cardinality(g);
563 
564         if (Blocked) {
565           // Cardinality must return 0
566           Block = 0;
567           Last = Nrec;
568           } // endif blocked
569 
570         // This will erase the entire file
571         strcpy(opmode, "w");
572         Tdbp->ResetSize();
573         break;
574         } // endif
575 
576       // Selective delete, pass thru
577       Bin = true;
578       /* fall through */
579     case MODE_UPDATE:
580       if ((UseTemp = Tdbp->IsUsingTemp(g))) {
581         strcpy(opmode, "r");
582         Bin = true;
583       } else
584         strcpy(opmode, "r+");
585 
586       break;
587     case MODE_INSERT:
588       strcpy(opmode, "a+");
589       break;
590     default:
591       sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
592       return true;
593     } // endswitch Mode
594 
595   // For blocked I/O or for moving lines, open the table in binary
596   strcat(opmode, (Bin) ? "b" : "t");
597 
598   // Now open the file stream
599   PlugSetPath(filename, To_File, Tdbp->GetPath());
600 
601   if (!(Stream = PlugOpenFile(g, filename, opmode))) {
602     if (trace(1))
603       htrc("%s\n", g->Message);
604 
605     return (mode == MODE_READ && errno == ENOENT)
606             ? PushWarning(g, Tdbp) : true;
607     } // endif Stream
608 
609   if (trace(1))
610     htrc("File %s open Stream=%p mode=%s\n", filename, Stream, opmode);
611 
612   To_Fb = dbuserp->Openlist;     // Keep track of File block
613 
614   /*********************************************************************/
615   /*  Allocate the line buffer. For mode Delete a bigger buffer has to */
616   /*  be allocated because is it also used to move lines into the file.*/
617   /*********************************************************************/
618   return AllocateBuffer(g);
619   } // end of OpenTableFile
620 
621 /***********************************************************************/
622 /*  Allocate the line buffer. For mode Delete a bigger buffer has to   */
623 /*  be allocated because is it also used to move lines into the file.  */
624 /***********************************************************************/
AllocateBuffer(PGLOBAL g)625 bool DOSFAM::AllocateBuffer(PGLOBAL g)
626   {
627   MODE mode = Tdbp->Mode;
628 
629   // Lrecl does not include line ending
630   Buflen = Lrecl + Ending + ((Bin) ? 1 : 0) + 1;     // Sergei
631 
632   if (trace(1))
633     htrc("SubAllocating a buffer of %d bytes\n", Buflen);
634 
635   To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
636 
637   if (UseTemp || mode == MODE_DELETE) {
638     // Have a big buffer to move lines
639     Dbflen = Buflen * DOS_BUFF_LEN;
640     DelBuf = PlugSubAlloc(g, NULL, Dbflen);
641   } else if (mode == MODE_INSERT) {
642     /*******************************************************************/
643     /*  Prepare the buffer so eventual gaps are filled with blanks.    */
644     /*******************************************************************/
645     memset(To_Buf, ' ', Buflen);
646     To_Buf[Buflen - 2] = '\n';
647     To_Buf[Buflen - 1] = '\0';
648   } // endif's mode
649 
650   return false;
651   } // end of AllocateBuffer
652 
653 /***********************************************************************/
654 /*  GetRowID: return the RowID of last read record.                    */
655 /***********************************************************************/
GetRowID(void)656 int DOSFAM::GetRowID(void)
657   {
658   return Rows;
659   } // end of GetRowID
660 
661 /***********************************************************************/
662 /*  GetPos: return the position of last read record.                   */
663 /***********************************************************************/
GetPos(void)664 int DOSFAM::GetPos(void)
665   {
666   return Fpos;
667   } // end of GetPos
668 
669 /***********************************************************************/
670 /*  GetNextPos: return the position of next record.                    */
671 /***********************************************************************/
GetNextPos(void)672 int DOSFAM::GetNextPos(void)
673   {
674   return ftell(Stream);
675   } // end of GetNextPos
676 
677 /***********************************************************************/
678 /*  SetPos: Replace the table at the specified position.               */
679 /***********************************************************************/
SetPos(PGLOBAL g,int pos)680 bool DOSFAM::SetPos(PGLOBAL g, int pos)
681   {
682   Fpos = pos;
683 
684   if (fseek(Stream, Fpos, SEEK_SET)) {
685     sprintf(g->Message, MSG(FSETPOS_ERROR), Fpos);
686     return true;
687     } // endif
688 
689   Placed = true;
690   return false;
691   } // end of SetPos
692 
693 /***********************************************************************/
694 /*  Record file position in case of UPDATE or DELETE.                  */
695 /***********************************************************************/
RecordPos(PGLOBAL g)696 bool DOSFAM::RecordPos(PGLOBAL g)
697   {
698   if ((Fpos = ftell(Stream)) < 0) {
699     sprintf(g->Message, MSG(FTELL_ERROR), 0, strerror(errno));
700 //  strcat(g->Message, " (possible wrong ENDING option value)");
701     return true;
702     } // endif Fpos
703 
704   return false;
705   } // end of RecordPos
706 
707 /***********************************************************************/
708 /*  Initialize Fpos and the current position for indexed DELETE.       */
709 /***********************************************************************/
InitDelete(PGLOBAL g,int fpos,int spos)710 int DOSFAM::InitDelete(PGLOBAL g, int fpos, int spos)
711   {
712   Fpos = fpos;
713 
714   if (fseek(Stream, spos, SEEK_SET)) {
715     sprintf(g->Message, MSG(FSETPOS_ERROR), Fpos);
716     return RC_FX;
717     } // endif
718 
719   return RC_OK;
720   } // end of InitDelete
721 
722 /***********************************************************************/
723 /*  Skip one record in file.                                           */
724 /***********************************************************************/
SkipRecord(PGLOBAL g,bool header)725 int DOSFAM::SkipRecord(PGLOBAL g, bool header)
726   {
727   PDBUSER dup = (PDBUSER)g->Activityp->Aptr;
728 
729   // Skip this record
730   if (!fgets(To_Buf, Buflen, Stream)) {
731     if (feof(Stream))
732       return RC_EF;
733 
734 #if defined(_WIN32)
735     sprintf(g->Message, MSG(READ_ERROR), To_File, _strerror(NULL));
736 #else
737     sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(0));
738 #endif
739     return RC_FX;
740     } // endif fgets
741 
742   // Update progress information
743   dup->ProgCur = GetPos();
744 
745   if (header) {
746     // For Delete
747     Fpos = ftell(Stream);
748 
749     if (!UseTemp)
750       Tpos = Spos = Fpos;     // No need to move header
751 
752     } // endif header
753 
754 #if defined(THREAD)
755   return RC_NF;                  // To have progress info
756 #else
757   return RC_OK;                  // To loop locally
758 #endif
759   } // end of SkipRecord
760 
761 /***********************************************************************/
762 /*  ReadBuffer: Read one line for a text file.                         */
763 /***********************************************************************/
ReadBuffer(PGLOBAL g)764 int DOSFAM::ReadBuffer(PGLOBAL g)
765   {
766   char *p;
767   int   rc;
768 
769   if (!Stream)
770     return RC_EF;
771 
772   if (trace(2))
773     htrc("ReadBuffer: Tdbp=%p To_Line=%p Placed=%d\n",
774                       Tdbp, Tdbp->To_Line, Placed);
775 
776   if (!Placed) {
777     /*******************************************************************/
778     /*  Record file position in case of UPDATE or DELETE.              */
779     /*******************************************************************/
780    next:
781     if (RecordPos(g))
782       return RC_FX;
783 
784     CurBlk = (int)Rows++;
785 
786     if (trace(2))
787       htrc("ReadBuffer: CurBlk=%d\n", CurBlk);
788 
789    /********************************************************************/
790     /*  Check whether optimization on ROWID                            */
791     /*  can be done, as well as for join as for local filtering.       */
792     /*******************************************************************/
793     switch (Tdbp->TestBlock(g)) {
794       case RC_EF:
795         return RC_EF;
796       case RC_NF:
797         // Skip this record
798         if ((rc = SkipRecord(g, FALSE)) != RC_OK)
799           return rc;
800 
801         goto next;
802       } // endswitch rc
803 
804   } else
805     Placed = false;
806 
807   if (trace(2))
808     htrc(" About to read: stream=%p To_Buf=%p Buflen=%d Fpos=%d\n",
809                           Stream, To_Buf, Buflen, Fpos);
810 
811   if (fgets(To_Buf, Buflen, Stream)) {
812     p = To_Buf + strlen(To_Buf) - 1;
813 
814     if (trace(2))
815       htrc(" Read: To_Buf=%p p=%c\n", To_Buf, p);
816 
817 #if defined(_WIN32)
818     if (Bin) {
819       // Data file is read in binary so CRLF remains
820 #else
821     if (true) {
822       // Data files can be imported from Windows (having CRLF)
823 #endif
824       if (*p == '\n' || *p == '\r') {
825         // is this enough for Unix ???
826         *p = '\0';          // Eliminate ending CR or LF character
827 
828         if (p > To_Buf) {
829           // is this enough for Unix ???
830           p--;
831 
832           if (*p == '\n' || *p == '\r')
833             *p = '\0';      // Eliminate ending CR or LF character
834 
835           } // endif To_Buf
836 
837         } // endif p
838 
839     } else if (*p == '\n')
840       *p = '\0';          // Eliminate ending new-line character
841 
842     if (trace(2))
843       htrc(" To_Buf='%s'\n", To_Buf);
844 
845     strcpy(Tdbp->To_Line, To_Buf);
846     num_read++;
847     rc = RC_OK;
848   } else if (feof(Stream)) {
849     rc = RC_EF;
850   } else {
851 #if defined(_WIN32)
852     sprintf(g->Message, MSG(READ_ERROR), To_File, _strerror(NULL));
853 #else
854     sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(0));
855 #endif
856 
857     if (trace(1))
858       htrc("%s\n", g->Message);
859 
860     rc = RC_FX;
861   } // endif's fgets
862 
863   if (trace(2))
864     htrc("ReadBuffer: rc=%d\n", rc);
865 
866   IsRead = true;
867   return rc;
868   } // end of ReadBuffer
869 
870 /***********************************************************************/
871 /*  WriteBuffer: File write routine for DOS access method.             */
872 /***********************************************************************/
873 int DOSFAM::WriteBuffer(PGLOBAL g)
874   {
875   int   curpos = 0;
876   bool  moved = true;
877 
878   // T_Stream is the temporary stream or the table file stream itself
879   if (!T_Stream) {
880     if (UseTemp && Tdbp->Mode == MODE_UPDATE) {
881       if (OpenTempFile(g))
882         return RC_FX;
883 
884     } else
885       T_Stream = Stream;
886 
887     } // endif T_Stream
888 
889   if (Tdbp->Mode == MODE_UPDATE) {
890     /*******************************************************************/
891     /*  Here we simply rewrite a record on itself. There are two cases */
892     /*  were another method should be used, a/ when Update apply to    */
893     /*  the whole file, b/ when updating the last field of a variable  */
894     /*  length file. The method could be to rewrite a new file, then   */
895     /*  to erase the old one and rename the new updated file.          */
896     /*******************************************************************/
897     curpos = ftell(Stream);
898 
899     if (trace(1))
900       htrc("Last : %d cur: %d\n", Fpos, curpos);
901 
902     if (UseTemp) {
903       /*****************************************************************/
904       /*  We are using a temporary file.                               */
905       /*  Before writing the updated record, we must eventually copy   */
906       /*  all the intermediate records that have not been updated.     */
907       /*****************************************************************/
908       if (MoveIntermediateLines(g, &moved))
909         return RC_FX;
910 
911       Spos = curpos;                            // New start position
912     } else
913       // Update is directly written back into the file,
914       //   with this (fast) method, record size cannot change.
915       if (fseek(Stream, Fpos, SEEK_SET)) {
916         sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
917         return RC_FX;
918         } // endif
919 
920     } // endif mode
921 
922   /*********************************************************************/
923   /*  Prepare the write the updated line.                              */
924   /*********************************************************************/
925   strcat(strcpy(To_Buf, Tdbp->To_Line), (Bin) ? CrLf : "\n");
926 
927   /*********************************************************************/
928   /*  Now start the writing process.                                   */
929   /*********************************************************************/
930   if ((fputs(To_Buf, T_Stream)) == EOF) {
931     sprintf(g->Message, MSG(FPUTS_ERROR), strerror(errno));
932     return RC_FX;
933     } // endif EOF
934 
935   if (Tdbp->Mode == MODE_UPDATE && moved)
936     if (fseek(Stream, curpos, SEEK_SET)) {
937       sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
938       return RC_FX;
939       } // endif
940 
941   if (trace(1))
942     htrc("write done\n");
943 
944   return RC_OK;
945   } // end of WriteBuffer
946 
947 /***********************************************************************/
948 /*  Data Base delete line routine for DOS and BLK access methods.      */
949 /***********************************************************************/
950 int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
951   {
952   bool moved;
953   int  curpos = ftell(Stream);
954 
955   /*********************************************************************/
956   /*  There is an alternative here:                                    */
957   /*  1 - use a temporary file in which are copied all not deleted     */
958   /*      lines, at the end the original file will be deleted and      */
959   /*      the temporary file renamed to the original file name.        */
960   /*  2 - directly move the not deleted lines inside the original      */
961   /*      file, and at the end erase all trailing records.             */
962   /*  This will be experimented.                                       */
963   /*********************************************************************/
964   if (trace(1))
965     htrc(
966   "DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
967                 irc, UseTemp, curpos, Fpos, Tpos, Spos);
968 
969   if (irc != RC_OK) {
970     /*******************************************************************/
971     /*  EOF: position Fpos at the end-of-file position.                */
972     /*******************************************************************/
973     fseek(Stream, 0, SEEK_END);
974     Fpos = ftell(Stream);
975 
976     if (trace(1))
977       htrc("Fpos placed at file end=%d\n", Fpos);
978 
979     } // endif irc
980 
981   if (Tpos == Spos) {
982     /*******************************************************************/
983     /*  First line to delete, Open temporary file.                     */
984     /*******************************************************************/
985     if (UseTemp) {
986       if (OpenTempFile(g))
987         return RC_FX;
988 
989     } else {
990       /*****************************************************************/
991       /*  Move of eventual preceding lines is not required here.       */
992       /*  Set the target file as being the source file itself.         */
993       /*  Set the future Tpos, and give Spos a value to block copying. */
994       /*****************************************************************/
995       T_Stream = Stream;
996       Spos = Tpos = Fpos;
997     } // endif UseTemp
998 
999     } // endif Tpos == Spos
1000 
1001   /*********************************************************************/
1002   /*  Move any intermediate lines.                                     */
1003   /*********************************************************************/
1004   if (MoveIntermediateLines(g, &moved))
1005     return RC_FX;
1006 
1007   if (irc == RC_OK) {
1008     /*******************************************************************/
1009     /*  Reposition the file pointer and set Spos.                      */
1010     /*******************************************************************/
1011     if (!UseTemp || moved)
1012       if (fseek(Stream, curpos, SEEK_SET)) {
1013         sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
1014         return RC_FX;
1015         } // endif
1016 
1017     Spos = GetNextPos();                     // New start position
1018 
1019     if (trace(1))
1020       htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
1021 
1022   } else {
1023     /*******************************************************************/
1024     /*  Last call after EOF has been reached.                          */
1025     /*  The UseTemp case is treated in CloseTableFile.                 */
1026     /*******************************************************************/
1027     if (!UseTemp & !Abort) {
1028       /*****************************************************************/
1029       /*  Because the chsize functionality is only accessible with a   */
1030       /*  system call we must close the file and reopen it with the    */
1031       /*  open function (_fopen for MS ??) this is still to be checked */
1032       /*  for compatibility with Text files and other OS's.            */
1033       /*****************************************************************/
1034       char filename[_MAX_PATH];
1035       int  h;                           // File handle, return code
1036 
1037       PlugSetPath(filename, To_File, Tdbp->GetPath());
1038       /*rc=*/ PlugCloseFile(g, To_Fb);
1039 
1040       if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
1041         return RC_FX;
1042 
1043       /*****************************************************************/
1044       /*  Remove extra records.                                        */
1045       /*****************************************************************/
1046 #if defined(_WIN32)
1047       if (chsize(h, Tpos)) {
1048         sprintf(g->Message, MSG(CHSIZE_ERROR), strerror(errno));
1049         close(h);
1050         return RC_FX;
1051         } // endif
1052 #else
1053       if (ftruncate(h, (off_t)Tpos)) {
1054         sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
1055         close(h);
1056         return RC_FX;
1057         } // endif
1058 #endif
1059 
1060       close(h);
1061 
1062       if (trace(1))
1063         htrc("done, h=%d irc=%d\n", h, irc);
1064 
1065       } // endif !UseTemp
1066 
1067   } // endif irc
1068 
1069   return RC_OK;                                      // All is correct
1070   } // end of DeleteRecords
1071 
1072 /***********************************************************************/
1073 /*  Open a temporary file used while updating or deleting.             */
1074 /***********************************************************************/
1075 bool DOSFAM::OpenTempFile(PGLOBAL g)
1076   {
1077   char tempname[_MAX_PATH];
1078   bool rc = false;
1079 
1080   /*********************************************************************/
1081   /*  Open the temporary file, Spos is at the beginning of file.       */
1082   /*********************************************************************/
1083   PlugSetPath(tempname, To_File, Tdbp->GetPath());
1084   strcat(PlugRemoveType(tempname, tempname), ".t");
1085 
1086   if (!(T_Stream = PlugOpenFile(g, tempname, "wb"))) {
1087     if (trace(1))
1088       htrc("%s\n", g->Message);
1089 
1090     rc = true;
1091   } else
1092     To_Fbt = PlgGetUser(g)->Openlist;
1093 
1094   return rc;
1095   } // end of OpenTempFile
1096 
1097 /***********************************************************************/
1098 /*  Move intermediate deleted or updated lines.                        */
1099 /*  This works only for file open in binary mode.                      */
1100 /***********************************************************************/
1101 bool DOSFAM::MoveIntermediateLines(PGLOBAL g, bool *b)
1102   {
1103   int   n;
1104   size_t req, len;
1105 
1106   for (*b = false, n = Fpos - Spos; n > 0; n -= req) {
1107     if (!UseTemp || !*b)
1108       if (fseek(Stream, Spos, SEEK_SET)) {
1109         sprintf(g->Message, MSG(READ_SEEK_ERROR), strerror(errno));
1110         return true;
1111         } // endif
1112 
1113     req = (size_t)MY_MIN(n, Dbflen);
1114     len = fread(DelBuf, 1, req, Stream);
1115 
1116     if (trace(1))
1117       htrc("after read req=%d len=%d\n", req, len);
1118 
1119     if (len != req) {
1120       sprintf(g->Message, MSG(DEL_READ_ERROR), (int) req, (int) len);
1121       return true;
1122       } // endif len
1123 
1124     if (!UseTemp)
1125       if (fseek(T_Stream, Tpos, SEEK_SET)) {
1126         sprintf(g->Message, MSG(WRITE_SEEK_ERR), strerror(errno));
1127         return true;
1128         } // endif
1129 
1130     if ((len = fwrite(DelBuf, 1, req, T_Stream)) != req) {
1131       sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
1132       return true;
1133       } // endif
1134 
1135     if (trace(1))
1136       htrc("after write pos=%d\n", ftell(Stream));
1137 
1138     Tpos += (int)req;
1139     Spos += (int)req;
1140 
1141     if (trace(1))
1142       htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);
1143 
1144     *b = true;
1145     } // endfor n
1146 
1147   return false;
1148   } // end of MoveIntermediate Lines
1149 
1150 /***********************************************************************/
1151 /*  Delete the old file and rename the new temp file.                  */
1152 /*  If aborting just delete the new temp file.                         */
1153 /*  If indexed, make the temp file from the arrays.                    */
1154 /***********************************************************************/
1155 int DOSFAM::RenameTempFile(PGLOBAL g)
1156   {
1157   char *tempname, filetemp[_MAX_PATH], filename[_MAX_PATH];
1158   int   rc = RC_OK;
1159 
1160   if (To_Fbt)
1161     tempname = (char*)To_Fbt->Fname;
1162   else
1163     return RC_INFO;               // Nothing to do ???
1164 
1165   // This loop is necessary because, in case of join,
1166   // To_File can have been open several times.
1167   for (PFBLOCK fb = PlgGetUser(g)->Openlist; fb; fb = fb->Next)
1168     if (fb == To_Fb || (fb == To_Fbt))
1169       rc = PlugCloseFile(g, fb);
1170 
1171   if (!Abort) {
1172     PlugSetPath(filename, To_File, Tdbp->GetPath());
1173     strcat(PlugRemoveType(filetemp, filename), ".ttt");
1174     remove(filetemp);   // May still be there from previous error
1175 
1176     if (rename(filename, filetemp)) {    // Save file for security
1177       snprintf(g->Message, MAX_STR, MSG(RENAME_ERROR),
1178               filename, filetemp, strerror(errno));
1179 			throw 51;
1180 		} else if (rename(tempname, filename)) {
1181       snprintf(g->Message, MAX_STR, MSG(RENAME_ERROR),
1182               tempname, filename, strerror(errno));
1183       rc = rename(filetemp, filename);   // Restore saved file
1184 			throw 52;
1185 		} else if (remove(filetemp)) {
1186       sprintf(g->Message, MSG(REMOVE_ERROR),
1187               filetemp, strerror(errno));
1188       rc = RC_INFO;                      // Acceptable
1189     } // endif's
1190 
1191   } else
1192     remove(tempname);
1193 
1194   return rc;
1195   } // end of RenameTempFile
1196 
1197 /***********************************************************************/
1198 /*  Table file close routine for DOS access method.                    */
1199 /***********************************************************************/
1200 void DOSFAM::CloseTableFile(PGLOBAL g, bool abort)
1201   {
1202   int rc;
1203 
1204   Abort = abort;
1205 
1206   if (UseTemp && T_Stream) {
1207     if (Tdbp->Mode == MODE_UPDATE && !Abort) {
1208       // Copy eventually remaining lines
1209       bool b;
1210 
1211       fseek(Stream, 0, SEEK_END);
1212       Fpos = ftell(Stream);
1213       Abort = MoveIntermediateLines(g, &b) != RC_OK;
1214       } // endif Abort
1215 
1216     // Delete the old file and rename the new temp file.
1217     rc = RenameTempFile(g);     // Also close all files
1218   } else {
1219     rc = PlugCloseFile(g, To_Fb);
1220 
1221     if (trace(1))
1222       htrc("DOS Close: closing %s rc=%d\n", To_File, rc);
1223 
1224   } // endif UseTemp
1225 
1226   Stream = NULL;           // So we can know whether table is open
1227   T_Stream = NULL;
1228   } // end of CloseTableFile
1229 
1230 /***********************************************************************/
1231 /*  Rewind routine for DOS access method.                              */
1232 /***********************************************************************/
1233 void DOSFAM::Rewind(void)
1234   {
1235   if (Stream)  // Can be NULL when making index on void table
1236     rewind(Stream);
1237 
1238   Rows = 0;
1239   OldBlk = CurBlk = -1;
1240   } // end of Rewind
1241 
1242 /* --------------------------- Class BLKFAM -------------------------- */
1243 
1244 /***********************************************************************/
1245 /*  Constructors.                                                      */
1246 /***********************************************************************/
1247 BLKFAM::BLKFAM(PDOSDEF tdp) : DOSFAM(tdp)
1248   {
1249   Blocked = true;
1250   Block = tdp->GetBlock();
1251   Last = tdp->GetLast();
1252   Nrec = tdp->GetElemt();
1253   Closing = false;
1254   BlkPos = tdp->GetTo_Pos();
1255   CurLine = NULL;
1256   NxtLine = NULL;
1257   OutBuf = NULL;
1258   } // end of BLKFAM standard constructor
1259 
1260 BLKFAM::BLKFAM(PBLKFAM txfp) : DOSFAM(txfp)
1261   {
1262   Closing = txfp->Closing;
1263   CurLine = txfp->CurLine;
1264   NxtLine = txfp->NxtLine;
1265   OutBuf = txfp->OutBuf;
1266   } // end of BLKFAM copy constructor
1267 
1268 /***********************************************************************/
1269 /*  Reset: reset position values at the beginning of file.             */
1270 /***********************************************************************/
1271 void BLKFAM::Reset(void)
1272   {
1273   DOSFAM::Reset();
1274   Closing = false;
1275   } // end of Reset
1276 
1277 /***********************************************************************/
1278 /*  Cardinality: returns table cardinality in number of rows.          */
1279 /*  This function can be called with a null argument to test the       */
1280 /*  availability of Cardinality implementation (1 yes, 0 no).          */
1281 /***********************************************************************/
1282 int BLKFAM::Cardinality(PGLOBAL g)
1283   {
1284   return (g) ? ((Block > 0) ? (int)((Block - 1) * Nrec + Last) : 0) : 1;
1285   } // end of Cardinality
1286 
1287 /***********************************************************************/
1288 /*  Use BlockTest to reduce the table estimated size.                  */
1289 /***********************************************************************/
1290 int BLKFAM::MaxBlkSize(PGLOBAL g, int)
1291   {
1292   int rc = RC_OK, savcur = CurBlk;
1293   int size;
1294 
1295   // Roughly estimate the table size as the sum of blocks
1296   // that can contain good rows
1297   for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++)
1298     if ((rc = Tdbp->TestBlock(g)) == RC_OK)
1299       size += (CurBlk == Block - 1) ? Last : Nrec;
1300     else if (rc == RC_EF)
1301       break;
1302 
1303   CurBlk = savcur;
1304   return size;
1305   } // end of MaxBlkSize
1306 
1307 /***********************************************************************/
1308 /*  Allocate the line buffer. For mode Delete or when a temp file is   */
1309 /*  used another big buffer has to be allocated because is it used     */
1310 /*  to move or update the lines into the (temp) file.                  */
1311 /***********************************************************************/
1312 bool BLKFAM::AllocateBuffer(PGLOBAL g)
1313   {
1314   int  len;
1315   MODE mode = Tdbp->GetMode();
1316 
1317   // For variable length files, Lrecl does not include CRLF
1318   len = Lrecl + ((Tdbp->GetFtype()) ? 0 : Ending);
1319   Buflen = len * Nrec;
1320   CurLine = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
1321 
1322   if (UseTemp || mode == MODE_DELETE) {
1323     if (mode == MODE_UPDATE)
1324       OutBuf = (char*)PlugSubAlloc(g, NULL, len + 1);
1325 
1326     Dbflen = Buflen;
1327     DelBuf = PlugSubAlloc(g, NULL, Dbflen);
1328   } else if (mode == MODE_INSERT)
1329     Rbuf = Nrec;                     // To be used by WriteDB
1330 
1331   return false;
1332   } // end of AllocateBuffer
1333 
1334 /***********************************************************************/
1335 /*  GetRowID: return the RowID of last read record.                    */
1336 /***********************************************************************/
1337 int BLKFAM::GetRowID(void)
1338   {
1339   return CurNum + Nrec * CurBlk + 1;
1340   } // end of GetRowID
1341 
1342 /***********************************************************************/
1343 /*  GetPos: return the position of last read record.                   */
1344 /***********************************************************************/
1345 int BLKFAM::GetPos(void)
1346   {
1347   return (CurNum + Nrec * CurBlk);          // Computed file index
1348   } // end of GetPos
1349 
1350 /***********************************************************************/
1351 /*  GetNextPos: called by DeleteRecords.                               */
1352 /***********************************************************************/
1353 int BLKFAM::GetNextPos(void)
1354   {
1355   return (int)(Fpos + NxtLine - CurLine);
1356   } // end of GetNextPos
1357 
1358 /***********************************************************************/
1359 /*  SetPos: Replace the table at the specified position.               */
1360 /***********************************************************************/
1361 bool BLKFAM::SetPos(PGLOBAL g, int)
1362   {
1363   strcpy(g->Message, "Blocked variable tables cannot be used indexed");
1364   return true;
1365   } // end of SetPos
1366 
1367 /***********************************************************************/
1368 /*  Record file position in case of UPDATE or DELETE.                  */
1369 /*  Not used yet for blocked tables.                                   */
1370 /***********************************************************************/
1371 bool BLKFAM::RecordPos(PGLOBAL)
1372   {
1373   Fpos = (CurNum + Nrec * CurBlk);          // Computed file index
1374   return false;
1375   } // end of RecordPos
1376 
1377 /***********************************************************************/
1378 /*  Skip one record in file.                                           */
1379 /***********************************************************************/
1380 int BLKFAM::SkipRecord(PGLOBAL, bool header)
1381   {
1382   if (header) {
1383     // For Delete
1384     Fpos = BlkPos[0];         // First block starts after the header
1385 
1386     if (!UseTemp)
1387       Tpos = Spos = Fpos;     // No need to move header
1388 
1389     } // endif header
1390 
1391   OldBlk = -2;        // To force fseek on first block
1392   return RC_OK;
1393   } // end of SkipRecord
1394 
1395 /***********************************************************************/
1396 /*  ReadBuffer: Read one line for a text file.                         */
1397 /***********************************************************************/
1398 int BLKFAM::ReadBuffer(PGLOBAL g)
1399   {
1400   int i, rc = RC_OK;
1401   size_t n;
1402 
1403   /*********************************************************************/
1404   /*  Sequential reading when Placed is not true.                      */
1405   /*********************************************************************/
1406   if (Placed) {
1407     Placed = false;
1408   } else if (++CurNum < Rbuf) {
1409     CurLine = NxtLine;
1410 
1411     // Get the position of the next line in the buffer
1412     while (*NxtLine++ != '\n') ;
1413 
1414     // Set caller line buffer
1415     n = NxtLine - CurLine - Ending;
1416     memcpy(Tdbp->GetLine(), CurLine, n);
1417     Tdbp->GetLine()[n] = '\0';
1418     goto fin;
1419   } else if (Rbuf < Nrec && CurBlk != -1) {
1420     return RC_EF;
1421   } else {
1422     /*******************************************************************/
1423     /*  New block.                                                     */
1424     /*******************************************************************/
1425     CurNum = 0;
1426 
1427    next:
1428     if (++CurBlk >= Block)
1429       return RC_EF;
1430 
1431     /*******************************************************************/
1432     /*  Before reading a new block, check whether block optimization   */
1433     /*  can be done, as well as for join as for local filtering.       */
1434     /*******************************************************************/
1435     switch (Tdbp->TestBlock(g)) {
1436       case RC_EF:
1437         return RC_EF;
1438       case RC_NF:
1439         goto next;
1440       } // endswitch rc
1441 
1442   } // endif's
1443 
1444   if (OldBlk == CurBlk)
1445     goto ok;         // Block is already there
1446 
1447   // fseek is required only in non sequential reading
1448   if (CurBlk != OldBlk + 1)
1449     if (fseek(Stream, BlkPos[CurBlk], SEEK_SET)) {
1450       sprintf(g->Message, MSG(FSETPOS_ERROR), BlkPos[CurBlk]);
1451       return RC_FX;
1452       } // endif fseek
1453 
1454   // Calculate the length of block to read
1455   BlkLen = BlkPos[CurBlk + 1] - BlkPos[CurBlk];
1456 
1457   if (trace(1))
1458     htrc("File position is now %d\n", ftell(Stream));
1459 
1460   // Read the entire next block
1461   n = fread(To_Buf, 1, (size_t)BlkLen, Stream);
1462 
1463   if ((size_t) n == (size_t) BlkLen) {
1464 //  ReadBlks++;
1465     num_read++;
1466     Rbuf = (CurBlk == Block - 1) ? Last : Nrec;
1467 
1468    ok:
1469     rc = RC_OK;
1470 
1471     // Get the position of the current line
1472     for (i = 0, CurLine = To_Buf; i < CurNum; i++)
1473       while (*CurLine++ != '\n') ;      // What about Unix ???
1474 
1475     // Now get the position of the next line
1476     for (NxtLine = CurLine; *NxtLine++ != '\n';) ;
1477 
1478     // Set caller line buffer
1479     n = NxtLine - CurLine - Ending;
1480     memcpy(Tdbp->GetLine(), CurLine, n);
1481     Tdbp->GetLine()[n] = '\0';
1482   } else if (feof(Stream)) {
1483     rc = RC_EF;
1484   } else {
1485 #if defined(_WIN32)
1486     sprintf(g->Message, MSG(READ_ERROR), To_File, _strerror(NULL));
1487 #else
1488     sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(errno));
1489 #endif
1490 
1491     if (trace(1))
1492       htrc("%s\n", g->Message);
1493 
1494     return RC_FX;
1495   } // endelse
1496 
1497   OldBlk = CurBlk;         // Last block actually read
1498   IsRead = true;           // Is read indeed
1499 
1500  fin:
1501   // Store the current record file position for Delete and Update
1502   Fpos = (int)(BlkPos[CurBlk] + CurLine - To_Buf);
1503   return rc;
1504   } // end of ReadBuffer
1505 
1506 /***********************************************************************/
1507 /*  WriteBuffer: File write routine for the blocked DOS access method. */
1508 /*  Update is directly written back into the file,                     */
1509 /*         with this (fast) method, record size cannot change.         */
1510 /***********************************************************************/
1511 int BLKFAM::WriteBuffer(PGLOBAL g)
1512   {
1513   if (Tdbp->GetMode() == MODE_INSERT) {
1514     /*******************************************************************/
1515     /*  In Insert mode, blocks are added sequentially to the file end. */
1516     /*******************************************************************/
1517     if (!Closing) {                    // Add line to the write buffer
1518       strcat(strcpy(CurLine, Tdbp->GetLine()), CrLf);
1519 
1520       if (++CurNum != Rbuf) {
1521         CurLine += strlen(CurLine);
1522         return RC_OK;                  // We write only full blocks
1523         } // endif CurNum
1524 
1525       } // endif Closing
1526 
1527     //  Now start the writing process.
1528     NxtLine = CurLine + strlen(CurLine);
1529     BlkLen = (int)(NxtLine - To_Buf);
1530 
1531     if (fwrite(To_Buf, 1, BlkLen, Stream) != (size_t)BlkLen) {
1532       sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
1533       Closing = true;      // To tell CloseDB about a Write error
1534       return RC_FX;
1535       } // endif size
1536 
1537     CurBlk++;
1538     CurNum = 0;
1539     CurLine = To_Buf;
1540   } else {
1541     /*******************************************************************/
1542     /*  Mode == MODE_UPDATE.                                           */
1543     /*******************************************************************/
1544     const char  *crlf;
1545     size_t len;
1546     int   curpos = ftell(Stream);
1547     bool   moved = true;
1548 
1549     // T_Stream is the temporary stream or the table file stream itself
1550     if (!T_Stream)
1551     {
1552       if (UseTemp /*&& Tdbp->GetMode() == MODE_UPDATE*/) {
1553         if (OpenTempFile(g))
1554           return RC_FX;
1555 
1556       } else
1557         T_Stream = Stream;
1558     }
1559     if (UseTemp) {
1560       /*****************************************************************/
1561       /*  We are using a temporary file. Before writing the updated    */
1562       /*  record, we must eventually copy all the intermediate records */
1563       /*  that have not been updated.                                  */
1564       /*****************************************************************/
1565       if (MoveIntermediateLines(g, &moved))
1566         return RC_FX;
1567 
1568       Spos = GetNextPos();                     // New start position
1569 
1570       // Prepare the output buffer
1571 #if defined(_WIN32)
1572       crlf = "\r\n";
1573 #else
1574       crlf = "\n";
1575 #endif   // _WIN32
1576       strcat(strcpy(OutBuf, Tdbp->GetLine()), crlf);
1577       len = strlen(OutBuf);
1578     } else {
1579       if (fseek(Stream, Fpos, SEEK_SET)) {   // Fpos is last position
1580         sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
1581         return RC_FX;
1582         } // endif fseek
1583 
1584       // Replace the line inside read buffer (length has not changed)
1585       memcpy(CurLine, Tdbp->GetLine(), strlen(Tdbp->GetLine()));
1586       OutBuf = CurLine;
1587       len = (size_t)(NxtLine - CurLine);
1588     } // endif UseTemp
1589 
1590     if (fwrite(OutBuf, 1, len, T_Stream) != (size_t)len) {
1591       sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
1592       return RC_FX;
1593       } // endif fwrite
1594 
1595     if (moved)
1596       if (fseek(Stream, curpos, SEEK_SET)) {
1597         sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
1598         return RC_FX;
1599         } // endif
1600 
1601   } // endif Mode
1602 
1603   return RC_OK;
1604   } // end of WriteBuffer
1605 
1606 /***********************************************************************/
1607 /*  Table file close routine for DOS access method.                    */
1608 /***********************************************************************/
1609 void BLKFAM::CloseTableFile(PGLOBAL g, bool abort)
1610   {
1611   int rc, wrc = RC_OK;
1612 
1613   Abort = abort;
1614 
1615   if (UseTemp && T_Stream) {
1616     if (Tdbp->GetMode() == MODE_UPDATE && !Abort) {
1617       // Copy eventually remaining lines
1618       bool b;
1619 
1620       fseek(Stream, 0, SEEK_END);
1621       Fpos = ftell(Stream);
1622       Abort = MoveIntermediateLines(g, &b) != RC_OK;
1623       } // endif Abort
1624 
1625     // Delete the old file and rename the new temp file.
1626     rc = RenameTempFile(g);    // Also close all files
1627   } else {
1628     // Closing is True if last Write was in error
1629     if (Tdbp->GetMode() == MODE_INSERT && CurNum && !Closing) {
1630       // Some more inserted lines remain to be written
1631       Rbuf = CurNum--;
1632       Closing = true;
1633       wrc = WriteBuffer(g);
1634     } else if (Modif && !Closing) {
1635       // Last updated block remains to be written
1636       Closing = true;
1637       wrc = ReadBuffer(g);
1638     } // endif's
1639 
1640     rc = PlugCloseFile(g, To_Fb);
1641 
1642     if (trace(1))
1643       htrc("BLK CloseTableFile: closing %s mode=%d wrc=%d rc=%d\n",
1644             To_File, Tdbp->GetMode(), wrc, rc);
1645 
1646   } // endif UseTemp
1647 
1648   Stream = NULL;           // So we can know whether table is open
1649   } // end of CloseTableFile
1650 
1651 /***********************************************************************/
1652 /*  Rewind routine for DOS access method.                              */
1653 /*  Note: commenting out OldBlk = -1 has two advantages:               */
1654 /*  1 - It forces fseek on  first block, thus suppressing the need to  */
1655 /*      rewind the file, anyway unuseful when second pass if indexed.  */
1656 /*  2 - It permit to avoid re-reading small tables having only 1 block.*/
1657 /***********************************************************************/
1658 void BLKFAM::Rewind(void)
1659   {
1660 //rewind(Stream);        will be placed by fseek
1661   CurBlk = -1;
1662   CurNum = Rbuf;
1663 //OldBlk = -1;     commented out in case we reuse last read block
1664 //Rbuf = 0;        commented out in case we reuse last read block
1665   } // end of Rewind
1666 
1667 /* --------------------------- Class BINFAM -------------------------- */
1668 
1669 #if 0
1670 /***********************************************************************/
1671 /*  BIN GetFileLength: returns file size in number of bytes.           */
1672 /***********************************************************************/
1673 int BINFAM::GetFileLength(PGLOBAL g)
1674 {
1675 	int len;
1676 
1677 	if (!Stream)
1678 		len = TXTFAM::GetFileLength(g);
1679 	else
1680 		if ((len = _filelength(_fileno(Stream))) < 0)
1681 			sprintf(g->Message, MSG(FILELEN_ERROR), "_filelength", To_File);
1682 
1683 	xtrc(1, "File length=%d\n", len);
1684 	return len;
1685 } // end of GetFileLength
1686 
1687 /***********************************************************************/
1688 /*  Cardinality: returns table cardinality in number of rows.          */
1689 /*  This function can be called with a null argument to test the       */
1690 /*  availability of Cardinality implementation (1 yes, 0 no).          */
1691 /***********************************************************************/
1692 int BINFAM::Cardinality(PGLOBAL g)
1693 {
1694 	return (g) ? -1 : 0;
1695 } // end of Cardinality
1696 
1697 /***********************************************************************/
1698 /*  OpenTableFile: Open a DOS/UNIX table file using C standard I/Os.   */
1699 /***********************************************************************/
1700 bool BINFAM::OpenTableFile(PGLOBAL g) {
1701 	char    opmode[4], filename[_MAX_PATH];
1702 	MODE    mode = Tdbp->GetMode();
1703 	PDBUSER dbuserp = PlgGetUser(g);
1704 
1705 	switch (mode) {
1706 	case MODE_READ:
1707 		strcpy(opmode, "rb");
1708 		break;
1709 	case MODE_WRITE:
1710 		strcpy(opmode, "wb");
1711 		break;
1712 	default:
1713 		sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
1714 		return true;
1715 	} // endswitch Mode
1716 
1717 	// Now open the file stream
1718 	PlugSetPath(filename, To_File, Tdbp->GetPath());
1719 
1720 	if (!(Stream = PlugOpenFile(g, filename, opmode))) {
1721 		if (trace(1))
1722 			htrc("%s\n", g->Message);
1723 
1724 		return (mode == MODE_READ && errno == ENOENT)
1725 			? PushWarning(g, Tdbp) : true;
1726 	} // endif Stream
1727 
1728 	if (trace(1))
1729 		htrc("File %s open Stream=%p mode=%s\n", filename, Stream, opmode);
1730 
1731 	To_Fb = dbuserp->Openlist;     // Keep track of File block
1732 
1733 	/*********************************************************************/
1734 	/*  Allocate the line buffer.                                        */
1735 	/*********************************************************************/
1736 	return AllocateBuffer(g);
1737 } // end of OpenTableFile
1738 #endif // 0
1739 
1740 /***********************************************************************/
1741 /*  Allocate the line buffer. For mode Delete a bigger buffer has to   */
1742 /*  be allocated because is it also used to move lines into the file.  */
1743 /***********************************************************************/
1744 bool BINFAM::AllocateBuffer(PGLOBAL g)
1745 {
1746   MODE mode = Tdbp->GetMode();
1747 
1748   // Lrecl is Ok
1749   Buflen = Lrecl;
1750 
1751   // Buffer will be allocated separately
1752   if (mode == MODE_ANY) {
1753     xtrc(1, "SubAllocating a buffer of %d bytes\n", Buflen);
1754     To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
1755   } else if (UseTemp || mode == MODE_DELETE) {
1756     // Have a big buffer to move lines
1757     Dbflen = Buflen * DOS_BUFF_LEN;
1758     DelBuf = PlugSubAlloc(g, NULL, Dbflen);
1759   } // endif mode
1760 
1761   return false;
1762 #if 0
1763   MODE mode = Tdbp->GetMode();
1764 
1765   // Lrecl is Ok
1766   Dbflen = Buflen = Lrecl;
1767 
1768   if (trace(1))
1769     htrc("SubAllocating a buffer of %d bytes\n", Buflen);
1770 
1771   DelBuf = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
1772   return false;
1773 #endif // 0
1774 } // end of AllocateBuffer
1775 
1776 #if 0
1777 /***********************************************************************/
1778 /*  GetRowID: return the RowID of last read record.                    */
1779 /***********************************************************************/
1780 int BINFAM::GetRowID(void) {
1781 	return Rows;
1782 } // end of GetRowID
1783 
1784 /***********************************************************************/
1785 /*  GetPos: return the position of last read record.                   */
1786 /***********************************************************************/
1787 int BINFAM::GetPos(void) {
1788 	return Fpos;
1789 } // end of GetPos
1790 
1791 /***********************************************************************/
1792 /*  GetNextPos: return the position of next record.                    */
1793 /***********************************************************************/
1794 int BINFAM::GetNextPos(void) {
1795 	return ftell(Stream);
1796 } // end of GetNextPos
1797 
1798 /***********************************************************************/
1799 /*  SetPos: Replace the table at the specified position.               */
1800 /***********************************************************************/
1801 bool BINFAM::SetPos(PGLOBAL g, int pos) {
1802 	Fpos = pos;
1803 
1804 	if (fseek(Stream, Fpos, SEEK_SET)) {
1805 		sprintf(g->Message, MSG(FSETPOS_ERROR), Fpos);
1806 		return true;
1807 	} // endif
1808 
1809 	Placed = true;
1810 	return false;
1811 } // end of SetPos
1812 
1813 /***********************************************************************/
1814 /*  Record file position in case of UPDATE or DELETE.                  */
1815 /***********************************************************************/
1816 bool BINFAM::RecordPos(PGLOBAL g) {
1817 	if ((Fpos = ftell(Stream)) < 0) {
1818 		sprintf(g->Message, MSG(FTELL_ERROR), 0, strerror(errno));
1819 		//  strcat(g->Message, " (possible wrong ENDING option value)");
1820 		return true;
1821 	} // endif Fpos
1822 
1823 	return false;
1824 } // end of RecordPos
1825 #endif // 0
1826 
1827 /***********************************************************************/
1828 /*  ReadBuffer: Read one line for a text file.                         */
1829 /***********************************************************************/
1830 int BINFAM::ReadBuffer(PGLOBAL g)
1831 {
1832 	int rc;
1833 
1834 	if (!Stream)
1835 		return RC_EF;
1836 
1837 	xtrc(2, "ReadBuffer: Tdbp=%p To_Line=%p Placed=%d\n",
1838 					Tdbp, Tdbp->GetLine(), Placed);
1839 
1840 	if (!Placed) {
1841 		/*******************************************************************/
1842 		/*  Record file position in case of UPDATE or DELETE.              */
1843 		/*******************************************************************/
1844 		if (RecordPos(g))
1845 			return RC_FX;
1846 
1847 		CurBlk = (int)Rows++;
1848 		xtrc(2, "ReadBuffer: CurBlk=%d\n", CurBlk);
1849 	} else
1850 		Placed = false;
1851 
1852 	xtrc(2, " About to read: bstream=%p To_Buf=%p Buflen=%d Fpos=%d\n",
1853 						Stream, To_Buf, Buflen, Fpos);
1854 
1855 	// Read the prefix giving the row length
1856 	if (!fread(&Recsize, sizeof(size_t), 1, Stream)) {
1857 		if (!feof(Stream)) {
1858 			strcpy(g->Message, "Error reading line prefix\n");
1859 			return RC_FX;
1860 		} else
1861 			return RC_EF;
1862 
1863 	} else if (Recsize > (unsigned)Buflen) {
1864 		sprintf(g->Message, "Record too big (Recsize=%zd Buflen=%d)\n", Recsize, Buflen);
1865 		return RC_FX;
1866 	}	// endif Recsize
1867 
1868 	if (fread(To_Buf, Recsize, 1, Stream)) {
1869 		xtrc(2, " Read: To_Buf=%p Recsize=%zd\n", To_Buf, Recsize);
1870 		num_read++;
1871 		rc = RC_OK;
1872 	} else if (feof(Stream)) {
1873 		rc = RC_EF;
1874 	} else {
1875 #if defined(_WIN32)
1876 		sprintf(g->Message, MSG(READ_ERROR), To_File, _strerror(NULL));
1877 #else
1878 		sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(0));
1879 #endif
1880 		xtrc(2, "%s\n", g->Message);
1881 		rc = RC_FX;
1882 	} // endif's fread
1883 
1884 	xtrc(2, "ReadBuffer: rc=%d\n", rc);
1885 	IsRead = true;
1886 	return rc;
1887 } // end of ReadBuffer
1888 
1889 /***********************************************************************/
1890 /*  WriteBuffer: File write routine for BIN access method.             */
1891 /***********************************************************************/
1892 int BINFAM::WriteBuffer(PGLOBAL g)
1893 {
1894 	int   curpos = 0;
1895 	bool  moved = true;
1896 
1897   // T_Stream is the temporary stream or the table file stream itself
1898   if (!T_Stream) {
1899     if (UseTemp && Tdbp->GetMode() == MODE_UPDATE) {
1900       if (OpenTempFile(g))
1901         return RC_FX;
1902 
1903     } else
1904       T_Stream = Stream;
1905 
1906   } // endif T_Stream
1907 
1908   if (Tdbp->GetMode() == MODE_UPDATE) {
1909     /*******************************************************************/
1910     /*  Here we simply rewrite a record on itself. There are two cases */
1911     /*  were another method should be used, a/ when Update apply to    */
1912     /*  the whole file, b/ when updating the last field of a variable  */
1913     /*  length file. The method could be to rewrite a new file, then   */
1914     /*  to erase the old one and rename the new updated file.          */
1915     /*******************************************************************/
1916     curpos = ftell(Stream);
1917 
1918     if (trace(1))
1919       htrc("Last : %d cur: %d\n", Fpos, curpos);
1920 
1921     if (UseTemp) {
1922       /*****************************************************************/
1923       /*  We are using a temporary file.                               */
1924       /*  Before writing the updated record, we must eventually copy   */
1925       /*  all the intermediate records that have not been updated.     */
1926       /*****************************************************************/
1927       if (MoveIntermediateLines(g, &moved))
1928         return RC_FX;
1929 
1930       Spos = curpos;                            // New start position
1931     } else
1932       // Update is directly written back into the file,
1933       //   with this (fast) method, record size cannot change.
1934       if (fseek(Stream, Fpos, SEEK_SET)) {
1935         sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
1936         return RC_FX;
1937       } // endif
1938 
1939   } // endif mode
1940 
1941   /*********************************************************************/
1942 	/*  Prepare writing the line.                                        */
1943 	/*********************************************************************/
1944 //memcpy(To_Buf, Tdbp->GetLine(), Recsize);
1945 
1946 	/*********************************************************************/
1947 	/*  Now start the writing process.                                   */
1948 	/*********************************************************************/
1949 	if (fwrite(&Recsize, sizeof(size_t), 1, T_Stream) != 1) {
1950 		sprintf(g->Message, "Error %d writing prefix to %s",
1951 			errno, To_File);
1952 		return RC_FX;
1953 	} else if (fwrite(To_Buf, Recsize, 1, T_Stream) != 1) {
1954 		sprintf(g->Message, "Error %d writing %zd bytes to %s",
1955 			errno, Recsize, To_File);
1956 		return RC_FX;
1957 	} // endif fwrite
1958 
1959   if (Tdbp->GetMode() == MODE_UPDATE && moved)
1960     if (fseek(Stream, curpos, SEEK_SET)) {
1961       sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
1962       return RC_FX;
1963     } // endif
1964 
1965   xtrc(1, "Binary write done\n");
1966 	return RC_OK;
1967 } // end of WriteBuffer
1968 
1969 #if 0
1970 /***********************************************************************/
1971 /*  Data Base delete line routine for DOS and BLK access methods.      */
1972 /***********************************************************************/
1973 int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
1974 {
1975   bool moved;
1976   int  curpos = ftell(Stream);
1977 
1978   /*********************************************************************/
1979   /*  There is an alternative here:                                    */
1980   /*  1 - use a temporary file in which are copied all not deleted     */
1981   /*      lines, at the end the original file will be deleted and      */
1982   /*      the temporary file renamed to the original file name.        */
1983   /*  2 - directly move the not deleted lines inside the original      */
1984   /*      file, and at the end erase all trailing records.             */
1985   /*  This will be experimented.                                       */
1986   /*********************************************************************/
1987   if (trace(1))
1988     htrc(
1989       "DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
1990       irc, UseTemp, curpos, Fpos, Tpos, Spos);
1991 
1992   if (irc != RC_OK) {
1993     /*******************************************************************/
1994     /*  EOF: position Fpos at the end-of-file position.                */
1995     /*******************************************************************/
1996     fseek(Stream, 0, SEEK_END);
1997     Fpos = ftell(Stream);
1998 
1999     if (trace(1))
2000       htrc("Fpos placed at file end=%d\n", Fpos);
2001 
2002   } // endif irc
2003 
2004   if (Tpos == Spos) {
2005     /*******************************************************************/
2006     /*  First line to delete, Open temporary file.                     */
2007     /*******************************************************************/
2008     if (UseTemp) {
2009       if (OpenTempFile(g))
2010         return RC_FX;
2011 
2012     } else {
2013       /*****************************************************************/
2014       /*  Move of eventual preceding lines is not required here.       */
2015       /*  Set the target file as being the source file itself.         */
2016       /*  Set the future Tpos, and give Spos a value to block copying. */
2017       /*****************************************************************/
2018       T_Stream = Stream;
2019       Spos = Tpos = Fpos;
2020     } // endif UseTemp
2021 
2022   } // endif Tpos == Spos
2023 
2024     /*********************************************************************/
2025     /*  Move any intermediate lines.                                     */
2026     /*********************************************************************/
2027   if (MoveIntermediateLines(g, &moved))
2028     return RC_FX;
2029 
2030   if (irc == RC_OK) {
2031     /*******************************************************************/
2032     /*  Reposition the file pointer and set Spos.                      */
2033     /*******************************************************************/
2034     if (!UseTemp || moved)
2035       if (fseek(Stream, curpos, SEEK_SET)) {
2036         sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
2037         return RC_FX;
2038       } // endif
2039 
2040     Spos = GetNextPos();                     // New start position
2041 
2042     if (trace(1))
2043       htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
2044 
2045   } else {
2046     /*******************************************************************/
2047     /*  Last call after EOF has been reached.                          */
2048     /*  The UseTemp case is treated in CloseTableFile.                 */
2049     /*******************************************************************/
2050     if (!UseTemp & !Abort) {
2051       /*****************************************************************/
2052       /*  Because the chsize functionality is only accessible with a   */
2053       /*  system call we must close the file and reopen it with the    */
2054       /*  open function (_fopen for MS ??) this is still to be checked */
2055       /*  for compatibility with Text files and other OS's.            */
2056       /*****************************************************************/
2057       char filename[_MAX_PATH];
2058       int  h;                           // File handle, return code
2059 
2060       PlugSetPath(filename, To_File, Tdbp->GetPath());
2061       /*rc=*/ PlugCloseFile(g, To_Fb);
2062 
2063       if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
2064         return RC_FX;
2065 
2066       /*****************************************************************/
2067       /*  Remove extra records.                                        */
2068       /*****************************************************************/
2069 #if defined(_WIN32)
2070       if (chsize(h, Tpos)) {
2071         sprintf(g->Message, MSG(CHSIZE_ERROR), strerror(errno));
2072         close(h);
2073         return RC_FX;
2074       } // endif
2075 #else
2076       if (ftruncate(h, (off_t)Tpos)) {
2077         sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
2078         close(h);
2079         return RC_FX;
2080       } // endif
2081 #endif
2082 
2083       close(h);
2084 
2085       if (trace(1))
2086         htrc("done, h=%d irc=%d\n", h, irc);
2087 
2088     } // endif !UseTemp
2089 
2090   } // endif irc
2091 
2092   return RC_OK;                                      // All is correct
2093 } // end of DeleteRecords
2094 
2095 /***********************************************************************/
2096 /*  Table file close routine for DOS access method.                    */
2097 /***********************************************************************/
2098 void BINFAM::CloseTableFile(PGLOBAL g, bool abort)
2099 {
2100   int rc;
2101 
2102   Abort = abort;
2103   rc = PlugCloseFile(g, To_Fb);
2104   xtrc(1, "BIN Close: closing %s rc=%d\n", To_File, rc);
2105   Stream = NULL;           // So we can know whether table is open
2106 } // end of CloseTableFile
2107 
2108 /***********************************************************************/
2109 /*  Rewind routine for BIN access method.                              */
2110 /***********************************************************************/
2111 void BINFAM::Rewind(void)
2112 {
2113 	if (Stream)  // Can be NULL when making index on void table
2114 		rewind(Stream);
2115 
2116 	Rows = 0;
2117 	OldBlk = CurBlk = -1;
2118 } // end of Rewind
2119 #endif // 0
2120