1 /*********** File AM Dbf C++ Program Source Code File (.CPP) ****************/
2 /* PROGRAM NAME: FILAMDBF */
3 /* ------------- */
4 /* Version 1.8 */
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 DBF file access method classes. */
13 /* */
14 /* ACKNOWLEDGEMENT: */
15 /* ---------------- */
16 /* Somerset Data Systems, Inc. (908) 766-5845 */
17 /* Version 1.2 April 6, 1991 */
18 /* Programmer: Jay Parsons */
19 /****************************************************************************/
20
21 /***********************************************************************/
22 /* Include relevant sections of the System header files. */
23 /***********************************************************************/
24 #include "my_global.h"
25 #if defined(_WIN32)
26 #include <io.h>
27 #include <fcntl.h>
28 //#include <errno.h>
29 //#include <windows.h>
30 #else // !_WIN32
31 #if defined(UNIX)
32 #include <errno.h>
33 #include <unistd.h>
34 #else // !UNIX
35 //#include <io.h>
36 #endif // !UNIX
37 //#include <fcntl.h>
38 #endif // !_WIN32
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <string.h>
42
43 /***********************************************************************/
44 /* Include application header files: */
45 /* global.h is header containing all global declarations. */
46 /* plgdbsem.h is header containing the DB application declarations. */
47 /* tabdos.h is header containing the TABDOS class declarations. */
48 /***********************************************************************/
49 #include "global.h"
50 #include "plgdbsem.h"
51 #include "filamdbf.h"
52 #include "filamzip.h"
53 #include "tabdos.h"
54 #include "valblk.h"
55 #define NO_FUNC
56 #include "plgcnx.h" // For DB types
57 #include "resource.h"
58
59 /****************************************************************************/
60 /* Definitions. */
61 /****************************************************************************/
62 #define HEADLEN 32 /* sizeof ( mainhead or thisfield ) */
63 //efine MEMOLEN 10 /* length of memo field in .dbf */
64 #define DBFTYPE 3 /* value of bits 0 and 1 if .dbf */
65 #define EOH 0x0D /* end-of-header marker in .dbf file */
66
67 /****************************************************************************/
68 /* First 32 bytes of a .dbf file. */
69 /* Note: some reserved fields are used here to store info (Fields) */
70 /****************************************************************************/
71 typedef struct _dbfheader {
72 //uchar Dbf :2; /* both 1 for dBASE III or IV .dbf */
73 //uchar :1;
74 //uchar Db4dbt:1; /* 1 if a dBASE IV-type .dbt exists */
75 //uchar Dbfox :4; /* FoxPro if equal to 3 */
76 uchar Version; /* Version information flags */
77 char Filedate[3]; /* date, YYMMDD, binary. YY=year-1900 */
78 private:
79 /* The following four members are stored in little-endian format on disk */
80 char m_RecordsBuf[4]; /* records in the file */
81 char m_HeadlenBuf[2]; /* bytes in the header */
82 char m_ReclenBuf[2]; /* bytes in a record */
83 char m_FieldsBuf[2]; /* Reserved but used to store fields */
84 public:
85 char Incompleteflag; /* 01 if incomplete, else 00 */
86 char Encryptflag; /* 01 if encrypted, else 00 */
87 char Reserved2[12]; /* for LAN use */
88 char Mdxflag; /* 01 if production .mdx, else 00 */
89 char Language; /* Codepage */
90 char Reserved3[2];
91
Records_dbfheader92 uint Records(void) const {return uint4korr(m_RecordsBuf);}
Headlen_dbfheader93 ushort Headlen(void) const {return uint2korr(m_HeadlenBuf);}
Reclen_dbfheader94 ushort Reclen(void) const {return uint2korr(m_ReclenBuf);}
Fields_dbfheader95 ushort Fields(void) const {return uint2korr(m_FieldsBuf);}
96
SetHeadlen_dbfheader97 void SetHeadlen(ushort num) {int2store(m_HeadlenBuf, num);}
SetReclen_dbfheader98 void SetReclen(ushort num) {int2store(m_ReclenBuf, num);}
SetFields_dbfheader99 void SetFields(ushort num) {int2store(m_FieldsBuf, num);}
100 } DBFHEADER;
101
102 /****************************************************************************/
103 /* Column field descriptor of a .dbf file. */
104 /****************************************************************************/
105 typedef struct _descriptor {
106 char Name[11]; /* field name, in capitals, null filled*/
107 char Type; /* field type, C, D, F, L, M or N */
108 uint Offset; /* used in memvars, not in files. */
109 uchar Length; /* field length */
110 uchar Decimals; /* number of decimal places */
111 short Reserved4;
112 char Workarea; /* ??? */
113 char Reserved5[2];
114 char Setfield; /* ??? */
115 char Reserved6[7];
116 char Mdxfield; /* 01 if tag field in production .mdx */
117 } DESCRIPTOR;
118
119 /****************************************************************************/
120 /* dbfhead: Routine to analyze a .dbf header. */
121 /* Parameters: */
122 /* PGLOBAL g -- pointer to the Plug Global structure */
123 /* FILE *file -- pointer to file to analyze */
124 /* PSZ fn -- pathname of the file to analyze */
125 /* DBFHEADER *buf -- pointer to _dbfheader structure */
126 /* Returns: */
127 /* RC_OK, RC_NF, RC_INFO, or RC_FX if error. */
128 /* Side effects: */
129 /* Moves file pointer to byte 32; fills buffer at buf with */
130 /* first 32 bytes of file. */
131 /****************************************************************************/
dbfhead(PGLOBAL g,FILE * file,PCSZ fn,DBFHEADER * buf)132 static int dbfhead(PGLOBAL g, FILE *file, PCSZ fn, DBFHEADER *buf)
133 {
134 char endmark[2];
135 int dbc = 2, rc = RC_OK;
136
137 *g->Message = '\0';
138
139 // Read the first 32 bytes into buffer
140 if (fread(buf, HEADLEN, 1, file) != 1) {
141 strcpy(g->Message, MSG(NO_READ_32));
142 return RC_NF;
143 } // endif fread
144
145 // Check first byte to be sure of .dbf type
146 if ((buf->Version & 0x03) != DBFTYPE) {
147 strcpy(g->Message, MSG(NOT_A_DBF_FILE));
148 rc = RC_INFO;
149
150 if ((buf->Version & 0x30) == 0x30) {
151 strcpy(g->Message, MSG(FOXPRO_FILE));
152 dbc = 264; // FoxPro database container
153 } // endif Version
154
155 } else
156 strcpy(g->Message, MSG(DBASE_FILE));
157
158 // Check last byte(s) of header
159 if (fseek(file, buf->Headlen() - dbc, SEEK_SET) != 0) {
160 sprintf(g->Message, MSG(BAD_HEADER), fn);
161 return RC_FX;
162 } // endif fseek
163
164 if (fread(&endmark, 2, 1, file) != 1) {
165 strcpy(g->Message, MSG(BAD_HEAD_END));
166 return RC_FX;
167 } // endif fread
168
169 // Some files have just 1D others have 1D00 following fields
170 if (endmark[0] != EOH && endmark[1] != EOH) {
171 sprintf(g->Message, MSG(NO_0DH_HEAD), dbc);
172
173 if (rc == RC_OK)
174 return RC_FX;
175
176 } // endif endmark
177
178 // Calculate here the number of fields while we have the dbc info
179 buf->SetFields((buf->Headlen() - dbc - 1) / 32);
180 fseek(file, HEADLEN, SEEK_SET);
181 return rc;
182 } // end of dbfhead
183
184 /****************************************************************************/
185 /* dbfields: Analyze a DBF header and set the table fields number. */
186 /* Parameters: */
187 /* PGLOBAL g -- pointer to the CONNECT Global structure */
188 /* DBFHEADER *hdrp -- pointer to _dbfheader structure */
189 /* Returns: */
190 /* RC_OK, RC_INFO, or RC_FX if error. */
191 /****************************************************************************/
dbfields(PGLOBAL g,DBFHEADER * hdrp)192 static int dbfields(PGLOBAL g, DBFHEADER* hdrp)
193 {
194 char* endmark;
195 int dbc = 2, rc = RC_OK;
196
197 *g->Message = '\0';
198
199 // Check first byte to be sure of .dbf type
200 if ((hdrp->Version & 0x03) != DBFTYPE) {
201 strcpy(g->Message, MSG(NOT_A_DBF_FILE));
202 rc = RC_INFO;
203
204 if ((hdrp->Version & 0x30) == 0x30) {
205 strcpy(g->Message, MSG(FOXPRO_FILE));
206 dbc = 264; // FoxPro database container
207 } // endif Version
208
209 } else
210 strcpy(g->Message, MSG(DBASE_FILE));
211
212 // Check last byte(s) of header
213 endmark = (char*)hdrp + hdrp->Headlen() - dbc;
214
215 // Some headers just have 1D others have 1D00 following fields
216 if (endmark[0] != EOH && endmark[1] != EOH) {
217 sprintf(g->Message, MSG(NO_0DH_HEAD), dbc);
218
219 if (rc == RC_OK)
220 return RC_FX;
221
222 } // endif endmark
223
224 // Calculate here the number of fields while we have the dbc info
225 hdrp->SetFields((hdrp->Headlen() - dbc - 1) / 32);
226 return rc;
227 } // end of dbfields
228
229 /* -------------------------- Function DBFColumns ------------------------- */
230
231 /****************************************************************************/
232 /* DBFColumns: constructs the result blocks containing the description */
233 /* of all the columns of a DBF file that will be retrieved by #GetData. */
234 /****************************************************************************/
DBFColumns(PGLOBAL g,PCSZ dp,PCSZ fn,PTOS topt,bool info)235 PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, PTOS topt, bool info)
236 {
237 int buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING,
238 TYPE_INT, TYPE_INT, TYPE_SHORT};
239 XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME,
240 FLD_PREC, FLD_LENGTH, FLD_SCALE};
241 unsigned int length[] = {11, 6, 8, 10, 10, 6};
242 char buf[2], filename[_MAX_PATH];
243 int ncol = sizeof(buftyp) / sizeof(int);
244 int rc, type, len, field, fields;
245 bool bad, mul;
246 PCSZ target, pwd;
247 DBFHEADER mainhead, *hp;
248 DESCRIPTOR thisfield, *tfp;
249 FILE *infile = NULL;
250 UNZIPUTL *zutp = NULL;
251 PQRYRES qrp;
252 PCOLRES crp;
253
254 if (trace(1))
255 htrc("DBFColumns: File %s\n", SVP(fn));
256
257 if (!info) {
258 if (!fn) {
259 strcpy(g->Message, MSG(MISSING_FNAME));
260 return NULL;
261 } // endif fn
262
263 /************************************************************************/
264 /* Open the input file. */
265 /************************************************************************/
266 PlugSetPath(filename, fn, dp);
267
268 if (topt->zipped) {
269 target = GetStringTableOption(g, topt, "Entry", NULL);
270 mul = (target && *target) ? strchr(target, '*') || strchr(target, '?')
271 : false;
272 mul = GetBooleanTableOption(g, topt, "Mulentries", mul);
273
274 if (mul) {
275 strcpy(g->Message, "Cannot find column definition for multiple entries");
276 return NULL;
277 } // endif Multiple
278
279 pwd = GetStringTableOption(g, topt, "Password", NULL);
280 zutp = new(g) UNZIPUTL(target, pwd, mul);
281
282 if (!zutp->OpenTable(g, MODE_READ, filename))
283 hp = (DBFHEADER*)zutp->memory;
284 else
285 return NULL;
286
287 /**********************************************************************/
288 /* Set the table fields number. */
289 /**********************************************************************/
290 if ((rc = dbfields(g, hp)) == RC_FX) {
291 zutp->close();
292 return NULL;
293 } // endif dbfields
294
295 tfp = (DESCRIPTOR*)hp;
296 } else {
297 if (!(infile = global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
298 return NULL;
299 else
300 hp = &mainhead;
301
302 /**********************************************************************/
303 /* Get the first 32 bytes of the header. */
304 /**********************************************************************/
305 if ((rc = dbfhead(g, infile, filename, hp)) == RC_FX) {
306 fclose(infile);
307 return NULL;
308 } // endif dbfhead
309
310 tfp = &thisfield;
311 } // endif zipped
312
313 /************************************************************************/
314 /* Get the number of the table fields. */
315 /************************************************************************/
316 fields = hp->Fields();
317 } else
318 fields = 0;
319
320 qrp = PlgAllocResult(g, ncol, fields, IDS_COLUMNS + 3,
321 buftyp, fldtyp, length, true, false);
322
323 if (info || !qrp) {
324 if (infile)
325 fclose(infile);
326 else if (zutp)
327 zutp->close();
328
329 return qrp;
330 } // endif info
331
332 if (trace(1)) {
333 htrc("Structure of %s\n", filename);
334 htrc("headlen=%hd reclen=%hd degree=%d\n",
335 hp->Headlen(), hp->Reclen(), fields);
336 htrc("flags(iem)=%d,%d,%d cp=%d\n", hp->Incompleteflag,
337 hp->Encryptflag, hp->Mdxflag, hp->Language);
338 htrc("%hd records, last changed %02d/%02d/%d\n",
339 hp->Records(), hp->Filedate[1], hp->Filedate[2],
340 hp->Filedate[0] + (hp->Filedate[0] <= 30) ? 2000 : 1900);
341 htrc("Field Type Offset Len Dec Set Mdx\n");
342 } // endif trace
343
344 buf[1] = '\0';
345
346 /**************************************************************************/
347 /* Do it field by field. We are at byte 32 of file. */
348 /**************************************************************************/
349 for (field = 0; field < fields; field++) {
350 bad = FALSE;
351
352 if (topt->zipped) {
353 tfp = (DESCRIPTOR*)((char*)tfp + HEADLEN);
354 } else if (fread(tfp, HEADLEN, 1, infile) != 1) {
355 sprintf(g->Message, MSG(ERR_READING_REC), field+1, fn);
356 goto err;
357 } // endif fread
358
359 len = tfp->Length;
360
361 if (trace(1))
362 htrc("%-11s %c %6ld %3d %2d %3d %3d\n",
363 tfp->Name, tfp->Type, tfp->Offset, len,
364 tfp->Decimals, tfp->Setfield, tfp->Mdxfield);
365
366 /************************************************************************/
367 /* Now get the results into blocks. */
368 /************************************************************************/
369 switch (tfp->Type) {
370 case 'C': // Characters
371 case 'L': // Logical 'T' or 'F' or space
372 type = TYPE_STRING;
373 break;
374 case 'M': // Memo a .DBT block number
375 case 'B': // Binary a .DBT block number
376 case 'G': // Ole a .DBT block number
377 type = TYPE_STRING;
378 break;
379 //case 'I': // Long
380 //case '+': // Autoincrement
381 // type = TYPE_INT;
382 // break;
383 case 'N':
384 type = (tfp->Decimals) ? TYPE_DOUBLE
385 : (len > 10) ? TYPE_BIGINT : TYPE_INT;
386 break;
387 case 'F': // Float
388 //case 'O': // Double
389 type = TYPE_DOUBLE;
390 break;
391 case 'D':
392 type = TYPE_DATE; // Is this correct ???
393 break;
394 default:
395 if (!info) {
396 sprintf(g->Message, MSG(BAD_DBF_TYPE), tfp->Type
397 , tfp->Name);
398 goto err;
399 } // endif info
400
401 type = TYPE_ERROR;
402 bad = TRUE;
403 } // endswitch Type
404
405 crp = qrp->Colresp; // Column Name
406 crp->Kdata->SetValue(tfp->Name, field);
407 crp = crp->Next; // Data Type
408 crp->Kdata->SetValue((int)type, field);
409 crp = crp->Next; // Type Name
410
411 if (bad) {
412 buf[0] = tfp->Type;
413 crp->Kdata->SetValue(buf, field);
414 } else
415 crp->Kdata->SetValue(GetTypeName(type), field);
416
417 crp = crp->Next; // Precision
418 crp->Kdata->SetValue((int)tfp->Length, field);
419 crp = crp->Next; // Length
420 crp->Kdata->SetValue((int)tfp->Length, field);
421 crp = crp->Next; // Scale (precision)
422 crp->Kdata->SetValue((int)tfp->Decimals, field);
423 } // endfor field
424
425 qrp->Nblin = field;
426
427 if (infile)
428 fclose(infile);
429 else if (zutp)
430 zutp->close();
431
432 #if 0
433 if (info) {
434 /************************************************************************/
435 /* Prepare return message for dbfinfo command. */
436 /************************************************************************/
437 char buf[64];
438
439 sprintf(buf,
440 "Ver=%02x ncol=%hu nlin=%u lrecl=%hu headlen=%hu date=%02d/%02d/%02d",
441 hp->Version, fields, hp->Records, hp->Reclen,
442 hp->Headlen, hp->Filedate[0], hp->Filedate[1],
443 hp->Filedate[2]);
444
445 strcat(g->Message, buf);
446 } // endif info
447 #endif // 0
448
449 /**************************************************************************/
450 /* Return the result pointer for use by GetData routines. */
451 /**************************************************************************/
452 return qrp;
453
454 err:
455 if (infile)
456 fclose(infile);
457 else if (zutp)
458 zutp->close();
459
460 return NULL;
461 } // end of DBFColumns
462
463 /* ---------------------------- Class DBFBASE ----------------------------- */
464
465 /****************************************************************************/
466 /* Constructors. */
467 /****************************************************************************/
DBFBASE(PDOSDEF tdp)468 DBFBASE::DBFBASE(PDOSDEF tdp)
469 {
470 Records = 0;
471 Nerr = 0;
472 Maxerr = tdp->Maxerr;
473 Accept = tdp->Accept;
474 ReadMode = tdp->ReadMode;
475 } // end of DBFBASE standard constructor
476
DBFBASE(DBFBASE * txfp)477 DBFBASE::DBFBASE(DBFBASE *txfp)
478 {
479 Records = txfp->Records;
480 Nerr = txfp->Nerr;
481 Maxerr = txfp->Maxerr;
482 Accept = txfp->Accept;
483 ReadMode = txfp->ReadMode;
484 } // end of DBFBASE copy constructor
485
486 /****************************************************************************/
487 /* ScanHeader: scan the DBF file header for number of records, record size,*/
488 /* and header length. Set Records, check that Reclen is equal to lrecl and */
489 /* return the header length or 0 in case of error. */
490 /****************************************************************************/
ScanHeader(PGLOBAL g,PCSZ fn,int lrecl,int * rln,PCSZ defpath)491 int DBFBASE::ScanHeader(PGLOBAL g, PCSZ fn, int lrecl, int *rln, PCSZ defpath)
492 {
493 int rc;
494 char filename[_MAX_PATH];
495 DBFHEADER header;
496 FILE *infile;
497
498 /************************************************************************/
499 /* Open the input file. */
500 /************************************************************************/
501 PlugSetPath(filename, fn, defpath);
502
503 if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
504 return 0; // Assume file does not exist
505
506 /************************************************************************/
507 /* Get the first 32 bytes of the header. */
508 /************************************************************************/
509 rc = dbfhead(g, infile, filename, &header);
510 fclose(infile);
511
512 if (rc == RC_NF) {
513 Records = 0;
514 return 0;
515 } else if (rc == RC_FX)
516 return -1;
517
518 *rln = (int)header.Reclen();
519 Records = (int)header.Records();
520 return (int)header.Headlen();
521 } // end of ScanHeader
522
523 /* ---------------------------- Class DBFFAM ------------------------------ */
524
525 /****************************************************************************/
526 /* Cardinality: returns table cardinality in number of rows. */
527 /* This function can be called with a null argument to test the */
528 /* availability of Cardinality implementation (1 yes, 0 no). */
529 /****************************************************************************/
Cardinality(PGLOBAL g)530 int DBFFAM::Cardinality(PGLOBAL g)
531 {
532 if (!g)
533 return 1;
534
535 if (!Headlen) {
536 int rln = 0; // Record length in the file header
537
538 Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
539
540 if (Headlen < 0)
541 return -1; // Error in ScanHeader
542
543 if (rln && Lrecl != rln) {
544 // This happens always on some Linux platforms
545 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, (ushort)rln);
546
547 if (Accept) {
548 Lrecl = rln;
549 Blksize = Nrec * rln;
550 PushWarning(g, Tdbp);
551 } else
552 return -1;
553
554 } // endif rln
555
556 } // endif Headlen
557
558 // Set number of blocks for later use
559 Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
560 return Records;
561 } // end of Cardinality
562
563 #if 0 // Not compatible with ROWID block optimization
564 /***********************************************************************/
565 /* GetRowID: return the RowID of last read record. */
566 /***********************************************************************/
567 int DBFFAM::GetRowID(void)
568 {
569 return Rows;
570 } // end of GetRowID
571 #endif
572
573 /***********************************************************************/
574 /* OpenTableFile: Open a DBF table file using C standard I/Os. */
575 /* Binary mode cannot be used on Insert because of EOF (CTRL+Z) char. */
576 /***********************************************************************/
OpenTableFile(PGLOBAL g)577 bool DBFFAM::OpenTableFile(PGLOBAL g)
578 {
579 char opmode[4], filename[_MAX_PATH];
580 //int ftype = Tdbp->GetFtype();
581 MODE mode = Tdbp->GetMode();
582 PDBUSER dbuserp = PlgGetUser(g);
583
584 switch (mode) {
585 case MODE_READ:
586 strcpy(opmode, "rb");
587 break;
588 case MODE_DELETE:
589 if (!Tdbp->GetNext()) {
590 // Store the number of deleted lines
591 DelRows = -1; // Means all lines deleted
592 // DelRows = Cardinality(g); no good because of soft deleted lines
593
594 // This will erase the entire file
595 strcpy(opmode, "w");
596 Tdbp->ResetSize();
597 Records = 0;
598 break;
599 } // endif
600
601 // Selective delete
602 /* fall through */
603 case MODE_UPDATE:
604 UseTemp = Tdbp->IsUsingTemp(g);
605 strcpy(opmode, (UseTemp) ? "rb" : "r+b");
606 break;
607 case MODE_INSERT:
608 // Must be in text mode to remove an eventual EOF character
609 strcpy(opmode, "a+");
610 break;
611 default:
612 sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
613 return true;
614 } // endswitch Mode
615
616 // Now open the file stream
617 PlugSetPath(filename, To_File, Tdbp->GetPath());
618
619 if (!(Stream = PlugOpenFile(g, filename, opmode))) {
620 if (trace(1))
621 htrc("%s\n", g->Message);
622
623 return (mode == MODE_READ && errno == ENOENT)
624 ? PushWarning(g, Tdbp) : true;
625 } // endif Stream
626
627 if (trace(1))
628 htrc("File %s is open in mode %s\n", filename, opmode);
629
630 To_Fb = dbuserp->Openlist; // Keep track of File block
631
632 /*********************************************************************/
633 /* Allocate the line buffer. For mode Delete a bigger buffer has to */
634 /* be allocated because is it also used to move lines into the file.*/
635 /*********************************************************************/
636 return AllocateBuffer(g);
637 } // end of OpenTableFile
638
639 /****************************************************************************/
640 /* Allocate the block buffer for the table. */
641 /****************************************************************************/
AllocateBuffer(PGLOBAL g)642 bool DBFFAM::AllocateBuffer(PGLOBAL g)
643 {
644 char c;
645 int rc;
646 MODE mode = Tdbp->GetMode();
647
648 Buflen = Blksize;
649 To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
650
651 if (mode == MODE_INSERT) {
652 #if defined(_WIN32)
653 /************************************************************************/
654 /* Now we can revert to binary mode in particular because the eventual */
655 /* writing of a new header must be done in binary mode to avoid */
656 /* translating 0A bytes (LF) into 0D0A (CRLF) by Windows in text mode. */
657 /************************************************************************/
658 if (_setmode(_fileno(Stream), _O_BINARY) == -1) {
659 sprintf(g->Message, MSG(BIN_MODE_FAIL), strerror(errno));
660 return true;
661 } // endif setmode
662 #endif // _WIN32
663
664 /************************************************************************/
665 /* If this is a new file, the header must be generated. */
666 /************************************************************************/
667 int len = GetFileLength(g);
668
669 if (!len) {
670 // Make the header for this DBF table file
671 struct tm *datm;
672 int hlen, n = 0;
673 ushort reclen = 1;
674 time_t t;
675 DBFHEADER *header;
676 DESCRIPTOR *descp;
677 PCOLDEF cdp;
678 PDOSDEF tdp = (PDOSDEF)Tdbp->GetDef();
679
680 // Count the number of columns
681 for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
682 if (!(cdp->Flags & U_SPECIAL)) {
683 reclen += cdp->GetLong();
684 n++;
685 } // endif Flags
686
687 if (Lrecl != reclen) {
688 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, reclen);
689
690 if (Accept) {
691 Lrecl = reclen;
692 Blksize = Nrec * Lrecl;
693 PushWarning(g, Tdbp);
694 } else
695 return true;
696
697 } // endif Lrecl
698
699 hlen = HEADLEN * (n + 1) + 2;
700 header = (DBFHEADER*)PlugSubAlloc(g, NULL, hlen);
701 memset(header, 0, hlen);
702 header->Version = DBFTYPE;
703 t = time(NULL) - (time_t)DTVAL::GetShift();
704 datm = gmtime(&t);
705 header->Filedate[0] = datm->tm_year - 100;
706 header->Filedate[1] = datm->tm_mon + 1;
707 header->Filedate[2] = datm->tm_mday;
708 header->SetHeadlen((ushort)hlen);
709 header->SetReclen(reclen);
710 descp = (DESCRIPTOR*)header;
711
712 // Currently only standard Xbase types are supported
713 for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
714 if (!(cdp->Flags & U_SPECIAL)) {
715 descp++;
716
717 switch ((c = *GetFormatType(cdp->GetType()))) {
718 case 'S': // Short integer
719 case 'L': // Large (big) integer
720 case 'T': // Tiny integer
721 c = 'N'; // Numeric
722 /* fall through */
723 case 'N': // Numeric (integer)
724 case 'F': // Float (double)
725 descp->Decimals = (uchar)cdp->F.Prec;
726 case 'C': // Char
727 case 'D': // Date
728 break;
729 default: // Should never happen
730 sprintf(g->Message, MSG(BAD_DBF_TYPE),
731 c, cdp->GetName());
732 return true;
733 } // endswitch c
734
735 strncpy(descp->Name, cdp->GetName(), 11);
736 descp->Type = c;
737 descp->Length = (uchar)cdp->GetLong();
738 } // endif Flags
739
740 *(char*)(++descp) = EOH;
741
742 // Now write the header
743 if (fwrite(header, 1, hlen, Stream) != (unsigned)hlen) {
744 sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
745 return true;
746 } // endif fwrite
747
748 Records = 0;
749 Headlen = hlen;
750 } else if (len < 0)
751 return true; // Error in GetFileLength
752
753 /************************************************************************/
754 /* For Insert the buffer must be prepared. */
755 /************************************************************************/
756 memset(To_Buf, ' ', Buflen);
757 Rbuf = Nrec; // To be used by WriteDB
758 } else if (UseTemp) {
759 // Allocate a separate buffer so block reading can be kept
760 Dbflen = Nrec;
761 DelBuf = PlugSubAlloc(g, NULL, Blksize);
762 } // endif's
763
764 if (!Headlen) {
765 /************************************************************************/
766 /* Here is a good place to process the DBF file header */
767 /************************************************************************/
768 DBFHEADER header;
769
770 if ((rc = dbfhead(g, Stream, Tdbp->GetFile(g), &header)) == RC_OK) {
771 if (Lrecl != (int)header.Reclen()) {
772 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, header.Reclen());
773
774 if (Accept) {
775 Lrecl = header.Reclen();
776 Blksize = Nrec * Lrecl;
777 PushWarning(g, Tdbp);
778 } else
779 return true;
780
781 } // endif Lrecl
782
783 Records = (int)header.Records();
784 Headlen = (int)header.Headlen();
785 } else if (rc == RC_NF) {
786 Records = 0;
787 Headlen = 0;
788 } else // RC_FX
789 return true; // Error in dbfhead
790
791 } // endif Headlen
792
793 /**************************************************************************/
794 /* Position the file at the begining of the data. */
795 /**************************************************************************/
796 if (Tdbp->GetMode() == MODE_INSERT)
797 rc = fseek(Stream, 0, SEEK_END);
798 else
799 rc = fseek(Stream, Headlen, SEEK_SET);
800
801 if (rc) {
802 sprintf(g->Message, MSG(BAD_DBF_FILE), Tdbp->GetFile(g));
803 return true;
804 } // endif fseek
805
806 return false;
807 } // end of AllocateBuffer
808
809 /***********************************************************************/
810 /* Reset buffer access according to indexing and to mode. */
811 /* >>>>>>>>>>>>>> TO BE RE-VISITED AND CHECKED <<<<<<<<<<<<<<<<<<<<<< */
812 /***********************************************************************/
ResetBuffer(PGLOBAL g)813 void DBFFAM::ResetBuffer(PGLOBAL g)
814 {
815 /*********************************************************************/
816 /* If access is random, performances can be much better when the */
817 /* reads are done on only one row, except for small tables that can */
818 /* be entirely read in one block. */
819 /*********************************************************************/
820 if (Tdbp->GetKindex() && ReadBlks != 1) {
821 Nrec = 1; // Better for random access
822 Rbuf = 0;
823 Blksize = Lrecl;
824 OldBlk = -2; // Has no meaning anymore
825 Block = Tdbp->Cardinality(g); // Blocks are one line now
826 } // endif Mode
827
828 } // end of ResetBuffer
829
830 /***********************************************************************/
831 /* ReadBuffer: Read one line for a DBF file. */
832 /***********************************************************************/
ReadBuffer(PGLOBAL g)833 int DBFFAM::ReadBuffer(PGLOBAL g)
834 {
835 if (!Placed && !Closing && GetRowID() == Records)
836 return RC_EF;
837
838 int rc = FIXFAM::ReadBuffer(g);
839
840 if (rc != RC_OK || Closing)
841 return rc;
842
843 switch (*Tdbp->GetLine()) {
844 case '*':
845 if (!ReadMode)
846 rc = RC_NF; // Deleted line
847 else
848 Rows++;
849
850 break;
851 case ' ':
852 if (ReadMode < 2)
853 Rows++; // Non deleted line
854 else
855 rc = RC_NF;
856
857 break;
858 default:
859 if (++Nerr >= Maxerr && !Accept) {
860 sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
861 rc = RC_FX;
862 } else
863 rc = (Accept) ? RC_OK : RC_NF;
864
865 } // endswitch To_Buf
866
867 return rc;
868 } // end of ReadBuffer
869
870 /***********************************************************************/
871 /* Copy the header into the temporary file. */
872 /***********************************************************************/
CopyHeader(PGLOBAL g)873 bool DBFFAM::CopyHeader(PGLOBAL g)
874 {
875 bool rc = true;
876
877 if (Headlen) {
878 void *hdr = PlugSubAlloc(g, NULL, Headlen);
879 size_t n, hlen = (size_t)Headlen;
880 int pos = ftell(Stream);
881
882 if (fseek(Stream, 0, SEEK_SET))
883 strcpy(g->Message, "Seek error in CopyHeader");
884 else if ((n = fread(hdr, 1, hlen, Stream)) != hlen)
885 sprintf(g->Message, MSG(BAD_READ_NUMBER), (int) n, To_File);
886 else if ((n = fwrite(hdr, 1, hlen, T_Stream)) != hlen)
887 sprintf(g->Message, MSG(WRITE_STRERROR), To_Fbt->Fname
888 , strerror(errno));
889 else if (fseek(Stream, pos, SEEK_SET))
890 strcpy(g->Message, "Seek error in CopyHeader");
891 else
892 rc = false;
893
894 } else
895 rc = false;
896
897 return rc;
898 } // end of CopyHeader
899
900 #if 0 // Not useful when UseTemp is false.
901 /***********************************************************************/
902 /* Mark the line to delete with '*' (soft delete). */
903 /* NOTE: this is not ready for UseTemp. */
904 /***********************************************************************/
905 int DBFFAM::InitDelete(PGLOBAL g, int fpos, int spos)
906 {
907 int rc = RC_FX;
908 size_t lrecl = (size_t)Lrecl;
909
910 if (Nrec != 1)
911 strcpy(g->Message, "Cannot delete in block mode");
912 else if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
913 sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
914 else if (fread(To_Buf, 1, lrecl, Stream) != lrecl)
915 sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(errno));
916 else
917 *To_Buf = '*';
918
919 if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
920 sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
921 else if (fwrite(To_Buf, 1, lrecl, Stream) != lrecl)
922 sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
923 else
924 rc = RC_NF; // Ok, Nothing else to do
925
926 return rc;
927 } // end of InitDelete
928 #endif // 0
929
930 /***********************************************************************/
931 /* Data Base delete line routine for DBF access methods. */
932 /* Deleted lines are just flagged in the first buffer character. */
933 /***********************************************************************/
DeleteRecords(PGLOBAL g,int irc)934 int DBFFAM::DeleteRecords(PGLOBAL g, int irc)
935 {
936 if (irc == RC_OK) {
937 // T_Stream is the temporary stream or the table file stream itself
938 if (!T_Stream)
939 {
940 if (UseTemp) {
941 if (OpenTempFile(g))
942 return RC_FX;
943
944 if (CopyHeader(g)) // For DBF tables
945 return RC_FX;
946
947 } else
948 T_Stream = Stream;
949 }
950 *Tdbp->GetLine() = '*';
951 Modif++; // Modified line in Delete mode
952 } // endif irc
953
954 return RC_OK;
955 } // end of DeleteRecords
956
957 /***********************************************************************/
958 /* Rewind routine for DBF access method. */
959 /***********************************************************************/
Rewind(void)960 void DBFFAM::Rewind(void)
961 {
962 BLKFAM::Rewind();
963 Nerr = 0;
964 } // end of Rewind
965
966 /***********************************************************************/
967 /* Table file close routine for DBF access method. */
968 /***********************************************************************/
CloseTableFile(PGLOBAL g,bool abort)969 void DBFFAM::CloseTableFile(PGLOBAL g, bool abort)
970 {
971 int rc = RC_OK, wrc = RC_OK;
972 MODE mode = Tdbp->GetMode();
973
974 Abort = abort;
975
976 // Closing is True if last Write was in error
977 if (mode == MODE_INSERT && CurNum && !Closing) {
978 // Some more inserted lines remain to be written
979 Rbuf = CurNum--;
980 // Closing = true;
981 wrc = WriteBuffer(g);
982 } else if (mode == MODE_UPDATE || mode == MODE_DELETE) {
983 if (Modif && !Closing) {
984 // Last updated block remains to be written
985 Closing = true;
986 wrc = WriteModifiedBlock(g);
987 } // endif Modif
988
989 if (UseTemp && T_Stream && wrc == RC_OK) {
990 if (!Abort) {
991 // Copy any remaining lines
992 bool b;
993
994 Fpos = Tdbp->Cardinality(g);
995 Abort = MoveIntermediateLines(g, &b) != RC_OK;
996 } // endif Abort
997
998 // Delete the old file and rename the new temp file.
999 RenameTempFile(g);
1000 goto fin;
1001 } // endif UseTemp
1002
1003 } // endif's mode
1004
1005 if (Tdbp->GetMode() == MODE_INSERT) {
1006 int n = ftell(Stream) - Headlen;
1007
1008 rc = PlugCloseFile(g, To_Fb);
1009
1010 if (n >= 0 && !(n % Lrecl)) {
1011 n /= Lrecl; // New number of lines
1012
1013 if (n > Records) {
1014 // Update the number of rows in the file header
1015 char filename[_MAX_PATH];
1016
1017 PlugSetPath(filename, To_File, Tdbp->GetPath());
1018 if ((Stream= global_fopen(g, MSGID_OPEN_MODE_STRERROR, filename, "r+b")))
1019 {
1020 char nRecords[4];
1021 int4store(nRecords, n);
1022
1023 fseek(Stream, 4, SEEK_SET); // Get header.Records position
1024 fwrite(nRecords, sizeof(nRecords), 1, Stream);
1025 fclose(Stream);
1026 Stream= NULL;
1027 Records= n; // Update Records value
1028 }
1029 } // endif n
1030
1031 } // endif n
1032
1033 } else // Finally close the file
1034 rc = PlugCloseFile(g, To_Fb);
1035
1036 fin:
1037 if (trace(1))
1038 htrc("DBF CloseTableFile: closing %s mode=%d wrc=%d rc=%d\n",
1039 To_File, mode, wrc, rc);
1040
1041 Stream = NULL; // So we can know whether table is open
1042 } // end of CloseTableFile
1043
1044 /* ---------------------------- Class DBMFAM ------------------------------ */
1045
1046 /****************************************************************************/
1047 /* Cardinality: returns table cardinality in number of rows. */
1048 /* This function can be called with a null argument to test the */
1049 /* availability of Cardinality implementation (1 yes, 0 no). */
1050 /****************************************************************************/
Cardinality(PGLOBAL g)1051 int DBMFAM::Cardinality(PGLOBAL g)
1052 {
1053 if (!g)
1054 return 1;
1055
1056 if (!Headlen) {
1057 int rln = 0; // Record length in the file header
1058
1059 Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
1060
1061 if (Headlen < 0)
1062 return -1; // Error in ScanHeader
1063
1064 if (rln && Lrecl != rln) {
1065 // This happens always on some Linux platforms
1066 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, (ushort)rln);
1067
1068 if (Accept) {
1069 Lrecl = rln;
1070 Blksize = Nrec * Lrecl;
1071 PushWarning(g, Tdbp);
1072 } else
1073 return -1;
1074
1075 } // endif rln
1076
1077 } // endif Headlen
1078
1079 // Set number of blocks for later use
1080 Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
1081 return Records;
1082 } // end of Cardinality
1083
1084 #if 0 // Not compatible with ROWID block optimization
1085 /***********************************************************************/
1086 /* GetRowID: return the RowID of last read record. */
1087 /***********************************************************************/
1088 int DBMFAM::GetRowID(void)
1089 {
1090 return Rows;
1091 } // end of GetRowID
1092 #endif
1093
1094 /***********************************************************************/
1095 /* Just check that on all deletion the unknown deleted line number is */
1096 /* sent back because Cardinality doesn't count soft deleted lines. */
1097 /***********************************************************************/
GetDelRows(void)1098 int DBMFAM::GetDelRows(void)
1099 {
1100 if (Tdbp->GetMode() == MODE_DELETE && !Tdbp->GetNext())
1101 return -1; // Means all lines deleted
1102 else
1103 return DelRows;
1104
1105 } // end of GetDelRows
1106
1107 /****************************************************************************/
1108 /* Allocate the block buffer for the table. */
1109 /****************************************************************************/
AllocateBuffer(PGLOBAL g)1110 bool DBMFAM::AllocateBuffer(PGLOBAL g)
1111 {
1112 if (!Headlen) {
1113 /************************************************************************/
1114 /* Here is a good place to process the DBF file header */
1115 /************************************************************************/
1116 DBFHEADER *hp = (DBFHEADER*)Memory;
1117
1118 if (Lrecl != (int)hp->Reclen()) {
1119 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, hp->Reclen());
1120
1121 if (Accept) {
1122 Lrecl = hp->Reclen();
1123 Blksize = Nrec * Lrecl;
1124 PushWarning(g, Tdbp);
1125 } else
1126 return true;
1127
1128 } // endif Lrecl
1129
1130 Records = (int)hp->Records();
1131 Headlen = (int)hp->Headlen();
1132 } // endif Headlen
1133
1134 /**************************************************************************/
1135 /* Position the file at the begining of the data. */
1136 /**************************************************************************/
1137 Fpos = Mempos = Memory + Headlen;
1138 Top--; // Because of EOF marker
1139 return false;
1140 } // end of AllocateBuffer
1141
1142 /****************************************************************************/
1143 /* ReadBuffer: Read one line for a FIX file. */
1144 /****************************************************************************/
ReadBuffer(PGLOBAL g)1145 int DBMFAM::ReadBuffer(PGLOBAL g)
1146 {
1147 // if (!Placed && GetRowID() == Records)
1148 // return RC_EF;
1149
1150 int rc = MPXFAM::ReadBuffer(g);
1151
1152 if (rc != RC_OK)
1153 return rc;
1154
1155 switch (*Fpos) {
1156 case '*':
1157 if (!ReadMode)
1158 rc = RC_NF; // Deleted line
1159 else
1160 Rows++;
1161
1162 break;
1163 case ' ':
1164 if (ReadMode < 2)
1165 Rows++; // Non deleted line
1166 else
1167 rc = RC_NF;
1168
1169 break;
1170 default:
1171 if (++Nerr >= Maxerr && !Accept) {
1172 sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
1173 rc = RC_FX;
1174 } else
1175 rc = (Accept) ? RC_OK : RC_NF;
1176 } // endswitch To_Buf
1177
1178 return rc;
1179 } // end of ReadBuffer
1180
1181 /****************************************************************************/
1182 /* Data Base delete line routine for DBF access methods. */
1183 /* Deleted lines are just flagged in the first buffer character. */
1184 /****************************************************************************/
DeleteRecords(PGLOBAL g,int irc)1185 int DBMFAM::DeleteRecords(PGLOBAL g, int irc)
1186 {
1187 if (irc == RC_OK)
1188 *Fpos = '*';
1189
1190 return RC_OK;
1191 } // end of DeleteRecords
1192
1193 /***********************************************************************/
1194 /* Rewind routine for DBF access method. */
1195 /***********************************************************************/
Rewind(void)1196 void DBMFAM::Rewind(void)
1197 {
1198 MBKFAM::Rewind();
1199 Nerr = 0;
1200 } // end of Rewind
1201
1202 /* --------------------------------- EOF ---------------------------------- */
1203