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