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