Repository navigation
Expand file tree
/
Copy pathodbci.cpp
More file actions
5977 lines (5142 loc) · 176 KB
/
Copy pathodbci.cpp
File metadata and controls
5977 lines (5142 loc) · 176 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
19980415 MvH blocked one error message
19990628 MvH+ljz experimental locking call
19990628 MvH temp defined DB_DEBUG and introduced experimental statement options
19990707 MvH undefined DB_DEBUG and removed statement options
19990827 MvH Added CreateIndex
20000629 ljz Logging of trouble now starts with '***'
20001105 MvH Run-time load ODBC routines for BC and WC
20001128 ljz Added InitializeCriticalSection in 'Open' and DeleteCriticalSection
in 'Close'
20010829 mvh Enabled DB_DEBUG to show db errors in verbose mode
20020412 mvh Use SQLExecDirectWithRetry to retry in case of any error (deadlock=1205)
Added CreateUniqueIndex function, and GetLastError
Ignore error from unique index to make multi-user safe
20020413 mvh NOTE: A SINGLE DB MAY NOT BE USED BY MULTIPLE THREADS
20020417 mvh Extended deadlock wait time to about 1 min for safety
20021014 mvh Added built-in dbf support for stable ODBC less-operation
Note: requires fully de-normalized database
notes: in dbsql, first bind row not set; in vrtosql; debug query print with %s in search crashes
20021016 mvh Added about 1 MB of in-memory hashed index in patient ID for all tables; speed OK to 10.000 objects
pack db on server start (for speed it is wise to keep DBF files on local harddisk)
20021017 mvh Made NeedPack a global for control from dgate; fix dbase_trunc; fix in Updaterecords;
fix first num field in AddRecord
20021020 mvh Made querymodes an array, so that each query field can be key, key% or %key%
Use querymode also when hashing the entries (part of db entry)
Fix process_escape for '' and \[ and also use it before dbase_write
20021028 mvh Set open retries shorter for normal readonly access
20021030 mvh Match alternative patientID fieldname such that built-in DBF queries work
20021113 mvh Added <= comparison in dbase_query (used for date comparison)
20030113 mvh Do not use () in sort order when multiple columns specified
20030128 mvh Added alternative field names for series
20030321 ljz Also handle 'write lock' for M$Access (NativeError 1205 for sql-server
equals -1102 in M$Access). Other dbf-drivers???
20030819 mvh Allow longer filenames
20031104 mvh Added \\ to process_escape
20040425 mvh Added DoubleBackSlashToDB control
20040520 mvh Speedup by fix dbaseIII hashing, setting hashfield was wrong
20040523 mvh Implemented in-memory index to speed dbase queries; added IndexDBF and PackDBF settings
20040528 mvh Fix built-in DbaseIII for > 2 GB; index fixed at 10.000.000 records
Fix problem when spaces in patient ID in index generation
20040528 mvh Added ExtractRecords (dbase III only); fix update/delete for record 0
20040530 mvh IndexDBF gives MB extra to allocate; skip dbf files starting with X
Convert normal queries in SeriesInstanceUID or StudyInstanceUID to indexed ones
20040601 mvh Added LongQueryDBF flag: report if query took longer than LongQueryDBF ms (default 1000)
Min 20 MB index during regen; allow pack on command (NeedPack=TRUE; DB.Open)
Increase query buffer size to 16384 (often reread), pack buffer to 65536
20040605 mvh Optimize speed for huge files, extract max 100000 records; made lockstart __int64
Do not lock for read in extract (is very slow for huge files)
Record lock no longer on physical bytes but on rec#; attempt to speed memory index
NeedPack==3 -> force pack
20040606 mvh Fix lock whole file; fix index patient ID database; fix in dbcreate for test
20040609 mvh Implemented fast in-memory locking and enabled for all shared files
20040610 mvh on dbase_create set indexcount to 0; dbase_trunc truncs indexcount
20040612 mvh put index creation in a thread; server runs (but does not find non yet indexed
records) during indexing; implemented indexdbf command NeedPack=4
20040614 mvh Added NeedPack=5: create non-threaded index (for archival options)
20040615 mvh Added warning when index is full; keep indices when creating db
20040930 mvh Added multiple value query; protect length of copy of where string
20041003 mvh Reused very long SQLStatement string for wheres string
For lenghts see dbsql.cpp; tructated debug printfs
set cols length to 4096; malloc tmp in query at 310000;
for length calculation see dbsql.cpp
20041013 mvh Used MAXQUERYLENGTH
20041029 mvh Fixes in multiple query (query string truncated + mode mix problem)
20041101 mvh DB is read only while in-memory indexing of DbaseIII active
20050102 mvh Small modifications to allow compile through total.cxx
20050107 mvh Export NumIndexing to tell dgate when indexing is done
20050116 mvh Adapted for use with LINUX (built-in dbase driver only)
Notes: 1) delay poorly implemented; 2) no support for >2 GB db
20050118 mvh Detach pthreads to avoid leaks
20050119 mvh Documented rare crash in Fill_Memory_Index
20050124 mvh Added support for huge files under Linux (not suitable for ext2 filesystem)
Allow = without spaces in query
20050205 mvh Added counters for db activity
20050206 mvh Small fixes to counters; allow "&" instead of " and " for CGI use
20050905 mvh Postgres code by Piotr Filipczuk checked by mvh, will not affect
other operations, but not tested yet (postgres will not compile on
my linux system)
20050907 mvh Merged change by Hans-Peter Hellemann: lseek64 not always defined, should be mapped by unistd.h
20050909 mvh Put check for = in query below >=: date range matching failed (Thanks Paolo Marcheschi)
20050911 mvh Removed postgres print statements
20051125 mvh Fixed BindField for postgres - now returns actual and not reserved length
20051217 mvh Maximum locks in dbaseIII driver from 1000 to 100 to speed up qunlock (20% regen speed)
20051218 mvh Respond to -b = NOTHREAD: run server as single process, useful for debugging and profiling
20051219 mvh Fixed slow leak of queryfields for built-in dbase driver
20051230 mvh Put index of DicomWorkList on AccessionNumber
20060219 mvh Start on native mysql driver
20060220 mvh Some more work: get correct length in bindfield; todo: user and password
20060226 mvh Runtime load MYSQL in WIN32
20060313 mvh Use username and password for mysql
20060402 mvh Start index threads with a short delay to give GUI priority to test startup
20060607 mvh Changed escape char processing for sql values (not queries) to match dbsql.cpp change
20060618 mvh Fix for Borland C++ builder
20060628 mvh Moved location of disabled query logging
20060630 mvh Speeded dbaseIII AddRecord lots and linux string handling
20060701 mvh Added and use mysql_fetch_lengths; protect query result
20060702 mvh Tested native mysql driver: does not like fast open/close
clonedb crashed after 4000 or so (avoided now)
20061212 mvh Found critical error in built-in dbase driver sql parser:
Adding patient with ID containing certain character sequences will
immediately overwrite the whole patient database. Queries with these
sequences will return too many records (less dangerous)
Issue not yet realy fixed but greatly reduced its likelyhood
20061213 mvh Fixed issue by replacing spaces inside strings in sql statement
with non breaking spaces. Only match on keywords and field names
which include spaces; replace with normal spaces in process_escape
20070123 mvh clear NativeError, State and Msg on db.open (e.g. for dbf that does not use them but prints)
20070126 mvh Added missing break in non breaking space field of process_escape: failed on multiple spaces in string
20070127 mvh NativeError etc only defined in win32 version
20070201 mvh DebugLevel 4 prints all sql activity
20070203 mvh Fixed >2GB file support in linux: use O_LARGEFILE and lseek64() - sorry Hans-Peter Hellemann
20070206 mvh Added CreateDatabase (created db and login)
20070207 mvh Removed creation of login from CreateDtabase for mysql and postgres
20070210 mvh Moved debug prints to SystemDebug; removed double log in QueryDistinct for dbaseIII
20070218 mvh Added SqLite (win32 dll version): seems to run now; very slow when sync, very fast async
20070307 mvh Made database type flag local, added Database(type) creator
20770308 bcb Fixed DARWIN ifs, made DARWIN/MySQL auto start.( MySQL is included with DARWIN )
20070315 mvh Merged; changed two nested ifs that had an else that would be wrong
20070406 mvh Use reentrant rand_r and localtime_r when available
20070507 mvh Native mysql does not correctly return empty fields (Steini)
20070702 mvh Fixed above fix; test version for Stephan Winter
20070703 mvh Removed test version code; display mysql connection errors on OperatorConsole
20070705 mvh Added DT_DBASEIIINOINDEX: disables indexing and packing
#define USESQLITE_DLL to load DLL; otherwise use statically linked sqlite3.c
Extented max number of dbf files to 20
20070709 mvh Fixed crash when mysql not started: format error in the error print
20070711 mvh Some fixes to compile with sqlite under linux
20070715 mvh Removed debug print statements for sqlite
20071027 mvh Maybe fix postgres error handling: reset ntuples
20071030 mvh More fixes to postgres error handling: missing returns in PrintLastError
20071115 mvh Fixed SQL database creation for SQL server 2005
20071118 mvh Adapted for 64 bits (use SQLLEN for BindField)
20071124 mvh Removed dangling WHERE and ORDER in SQL server interface when passing "" instead of NULL
20081116 mvh Adapted for 64 bits linux (avoid use of "long", in dbase code and when reading SQL_C_ULONG)
20081117 mvh Allow compile with both POSTGRES and USEMYSQL
20081119 mvh added run-time loading of postgres for WIN32
20081120 mvh Added UseEscapeStringConstants for dbase (is needed only in one place because in other
place the E in E'\\...' is igored by the current code)
20081121 mvh Implemented CreateDatabase for Postgres; also try load libmysql64.dll (for easy 64 bit deployment)
20081124 mvh Added DT_NULL (black hole)
20081125 mvh Fix critical section in DT_NULL
20081126 mvh Todo: all realloc of dbase index when full; don't use threads to fill
20090202 mvh Fixed problem of database fail open in GUI after install:
XADICOMPatients was never closed, as connected was not set when there were no databases
20090203 mvh Fix crash on SQLite error
20090429 mvh Added libpq64.dll
20090616 mvh Added OldUID as hashfield: indices UIDMods table; increased default index to 10 MB
20090620 jf Include file stuff
20091230 mvh Merged bcb change
20091231 bcb Changed char* to const char* and added blank[]="" for gcc4.2 warnings
20100111 mvh Merged
20100123 mvh Allow host:port syntax for hostname in MySQL and PostGres
20100125 mvh GetPrivateProfileString -> MyGetPrivateProfileString
20100309 bcb Cast k to unsigned int, commented out unused variables (gcc4.2 Warnings)
20100309 bcb Added retunn FALSE to the end of CreateDatabase
20100309 bcb Added double parentheses (gcc4.2 Warnings)
20100619 bcb Fix gcc4 warnings, improve speed and added #ifndefs and UNUSED_ARGUMENT.
20100703 mvh Merged; but fixed random seed change (initialize static)
20100717 mvh Merged
20100815 mvh Fixed linux compile for SqLite
20100816 mvh removed (a) from #ifndef EnterCriticalSection(a) etc
20101121 mvh use strncpy to avoid access errors in Sqlite where fields may exceed defined length
20101121 mvh Disallow SqLite with DoubleBackSlashToDB
20101129 mvh Filter [x] like sequences in dbaseIII driver process_escape
20110102 mvh retry connect to postgres once; more postgres error handling
20110104 mvh Undone [x] change of 20101129
20110305 mvh Replaced Sleep(555) in linux code by delay(555)
20110319 mvh Normalized line endings
20110502 mvh Added db:Exec
20111113 mvh Fixed leak in DT_NULL driver (SQLStatement not freed)
20120107 mvh Accept 64 bits DLL's also as e.g., x64\libpq.dll
20131029 mvh typo in lPassword
*/
/*
Hi Stephan,
it is a mysql/windows issue: mysql uses temporary ports that are not
released quickly enough. Mysql dies after several thousand
connects/disconnects because all temporary ports are allocated. With the
debug log, the server is slightly slower and the problem does not happen.
See:
http://support.microsoft.com/kb/q196271/
http://bugs.mysql.com/bug.php?id=6580
http://bugs.mysql.com/bug.php?id=10498
Quotes:
2. Locate the following subkey in the registry, and then click Parameters:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
3. On the Edit menu, click New, and then add the following registry entry:
Value Name: MaxUserPort
Value Type: DWORD
Value data: 65534
Valid Range: 5000-65534 (decimal)
Default: 0x1388 (5000 decimal)
Description: This parameter controls the maximum port number that is used
when a program requests any available user port from the system. Typically
, ephemeral (short-lived) ports are allocated between the values of 1024
and 5000 inclusive.
4. Quit Registry Editor.
Note An additional TCPTimedWaitDelay registry parameter determines how
long a closed port waits until the closed port can be reused.
So in the registry:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
set:
MaxUserPort = 65534
TcpTimedWaitDelay = 30
*/
#ifndef DEBUG_MODE
# define DEBUG_MODE
#endif
//#define USESQLITE_DLL
#ifndef UNUSED_ARGUMENT
#define UNUSED_ARGUMENT(x) (void)x
#endif
#ifdef WIN32
# include <stdio.h>
# include <windows.h>
# include <sql.h>
# include <sqlext.h>
#endif
//#ifdef POSTGRES
//# include "libpq-fe.h"
//#endif
#include <string.h> //DUCKHEAD92
#include <ctype.h> //DUCKHEAD92
#include <time.h> //DUCKHEAD92
#ifdef WIN32
# include <io.h>
# include <sys\stat.h>
# include <sys/locking.h>
# include <dos.h>
# define delay Sleep
# define O_LARGEFILE 0
#else
# include <unistd.h>
# include <dirent.h>
# define delay(a) sleep(1)
# define O_BINARY 0
# define sopen(a, b, c, d) open(a, b)
#ifndef EnterCriticalSection
# define EnterCriticalSection(a) pthread_mutex_lock(a)
#endif
#ifndef LeaveCriticalSection
# define LeaveCriticalSection(a) pthread_mutex_unlock(a)
#endif
#ifndef CRITICAL_SECTION
# define CRITICAL_SECTION pthread_mutex_t
#endif
#ifndef InitializeCriticalSection
# define InitializeCriticalSection(a) pthread_mutex_init(a, NULL);
#endif
#ifndef DeleteCriticalSection
# define DeleteCriticalSection(a) pthread_mutex_destroy(a);
#endif
void strupr(char *s)
{ int L=strlen(s); for (int i=0; i<L; i++) s[i]=toupper(s[i]);
}
void strlwr(char *s)
{ int L=strlen(s); for (int i=0; i<L; i++) s[i]=tolower(s[i]);
}
int GetTickCount()
{ return time(NULL)*1000;
}
#define WINAPI
#ifdef BSD
# define O_LARGEFILE 0
#endif //BSD
#endif
# include <fcntl.h>
# include <errno.h>
# ifndef _LK_NBLCK
# define _LK_NBLCK LK_NBLCK
# define _LK_UNLCK LK_UNLCK
# endif
# if !defined(_errno)
# define _errno errno
# endif
# define open_retry_count 4
# define open_retry_delay 50
# define lock_retry_count 4
# define lock_retry_delay 50
# define file_lock_error -1
# define file_access_error 5
#include "gpps.hpp" //DUCKHEAD92
#include "dgate.hpp"
int NeedPack = TRUE; // controls pack of dbase files on startup
static int OdbcDoubleBackSlashToDB=0;
static int OdbcUseEscapeStringConstants=0;
static int PackDBF=0;
static int IndexDBF=0;
static int LongQueryDBF=1000;
int NumIndexing=-1; // is >0 during in-memory indexing
extern BOOL NoThread; // debug non-threaded
static short *Indices[20] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
static int IndicesCount[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// end for built-in dbase support
# include "odbci.hpp"
# include "dicom.hpp"
# include "dprintf.hpp"
typedef unsigned char BYTE;
typedef unsigned int UINT;
#ifndef DB_DEBUG
# define DB_DEBUG
#endif
# define QUERY 1
# define INSERT 2
#if defined(__BORLANDC__) || defined(__WATCOMC__)
typedef SQLRETURN (SQL_API *_SQLAllocConnect)(SQLHENV EnvironmentHandle,
SQLHDBC *ConnectionHandle);
typedef SQLRETURN (SQL_API *_SQLAllocEnv)(SQLHENV *EnvironmentHandle);
typedef SQLRETURN (SQL_API *_SQLAllocStmt)(SQLHDBC ConnectionHandle,
SQLHSTMT *StatementHandle);
typedef SQLRETURN (SQL_API *_SQLConnect)(SQLHDBC ConnectionHandle,
SQLCHAR *ServerName, SQLSMALLINT NameLength1,
SQLCHAR *UserName, SQLSMALLINT NameLength2,
SQLCHAR *Authentication, SQLSMALLINT NameLength3);
typedef SQLRETURN (SQL_API *_SQLDisconnect)(SQLHDBC ConnectionHandle);
typedef SQLRETURN (SQL_API *_SQLFreeConnect)(SQLHDBC ConnectionHandle);
typedef SQLRETURN (SQL_API *_SQLFreeEnv)(SQLHENV EnvironmentHandle);
typedef SQLRETURN (SQL_API *_SQLFreeStmt)(SQLHSTMT StatementHandle,
SQLUSMALLINT Option);
typedef SQLRETURN (SQL_API *_SQLTables)(SQLHSTMT StatementHandle,
SQLCHAR *CatalogName, SQLSMALLINT NameLength1,
SQLCHAR *SchemaName, SQLSMALLINT NameLength2,
SQLCHAR *TableName, SQLSMALLINT NameLength3,
SQLCHAR *TableType, SQLSMALLINT NameLength4);
typedef SQLRETURN (SQL_API *_SQLBindCol)(SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType,
SQLPOINTER TargetValue, SQLINTEGER BufferLength,
SQLINTEGER *StrLen_or_Ind);
typedef SQLRETURN (SQL_API *_SQLFetch)(SQLHSTMT StatementHandle);
typedef SQLRETURN (SQL_API *_SQLError)(SQLHENV EnvironmentHandle,
SQLHDBC ConnectionHandle, SQLHSTMT StatementHandle,
SQLCHAR *Sqlstate, SQLINTEGER *NativeError,
SQLCHAR *MessageText, SQLSMALLINT BufferLength,
SQLSMALLINT *TextLength);
typedef SQLRETURN (SQL_API *_SQLExecDirect)(SQLHSTMT StatementHandle,
SQLCHAR *StatementText, SQLINTEGER TextLength);
_SQLAllocConnect __SQLAllocConnect;
_SQLAllocEnv __SQLAllocEnv;
_SQLAllocStmt __SQLAllocStmt;
_SQLConnect __SQLConnect;
_SQLDisconnect __SQLDisconnect;
_SQLFreeConnect __SQLFreeConnect;
_SQLFreeEnv __SQLFreeEnv;
_SQLFreeStmt __SQLFreeStmt;
_SQLTables __SQLTables;
_SQLBindCol __SQLBindCol;
_SQLFetch __SQLFetch;
_SQLError __SQLError;
_SQLExecDirect __SQLExecDirect;
#endif
// for built-in dbase support
// code for in-memory record locking
struct LOCK_DATA
{ int start;
int end;
char filename[512];
};
CRITICAL_SECTION lock_critical;
int lock_count = 100;
struct LOCK_DATA *lock_table = NULL;
// wait for lock to be passed and/or place lock in the lock table
int qlock(char *filename, int start, int len)
{ int i, lock_failure=1, end=start+len-1;
// initialize
if (lock_table==NULL)
{ lock_table = (struct LOCK_DATA*)malloc(lock_count * sizeof(LOCK_DATA));
memset(lock_table, 0, lock_count * sizeof(LOCK_DATA));
InitializeCriticalSection(&lock_critical);
}
// scan all locks
EnterCriticalSection(&lock_critical);
for (i=0; i<lock_count; i++)
{ if (lock_table[i].start==0 && lock_table[i].end==0)
{ lock_table[i].start=start;
lock_table[i].end=end;
strcpy(lock_table[i].filename, filename);
lock_failure=0;
break;
}
if (start<lock_table[i].start && end<lock_table[i].start) continue;
if (start>lock_table[i].end && end>lock_table[i].end ) continue;
if (stricmp(lock_table[i].filename, filename)) continue;
break;
}
LeaveCriticalSection(&lock_critical);
return lock_failure;
}
// unlock earlier made entry in the lock table
int qunlock(char *filename, int start, int len)
{ int i, unlock_failure=1, end=start+len-1;//, j;
// initialize
if (lock_table==NULL)
{ lock_table = (struct LOCK_DATA*)malloc(lock_count * sizeof(LOCK_DATA));
memset(lock_table, 0, lock_count * sizeof(LOCK_DATA));
InitializeCriticalSection(&lock_critical);
}
// scan all locks
EnterCriticalSection(&lock_critical);
for (i=0; i<lock_count; i++)
{ if (lock_table[i].start!=start) continue;
if (lock_table[i].end !=end ) continue;
if (stricmp(lock_table[i].filename, filename)) continue;
lock_table[i].start=lock_table[i].end=0;
unlock_failure=0;
break;
}
LeaveCriticalSection(&lock_critical);
return unlock_failure;
}
#if defined(__BORLANDC__)
#define _lseeki64 lseek
#endif
#ifdef BSD
#define lseek64 lseek
#endif //BSD
#ifdef WIN32
static void LongerSeek(int fh, int rec, int mult, int add)
{ __int64 t;
t = (__int64)rec * (__int64)mult + (__int64)add;
if (t>(__int64)0x7fff * (__int64)0xffff)
_lseeki64(fh, t, SEEK_SET);
else
lseek(fh, t, SEEK_SET);
}
#else
static void LongerSeek(int fh, int rec, int mult, int add)
#ifdef __GNUC__ //ISO C++ does not support long long (and my long long was 32).
{ uint64_t t;
t = (uint64_t)rec * (uint64_t)mult + (uint64_t)add;
#else
{ long long t;
t = (long long)rec * (long long)mult + (long long)add;
#endif
// lseek(fh, t, SEEK_SET);
// unistd.h should map lseek to
lseek64(fh, t, SEEK_SET);
}
#endif
//static int qlock(int fh, __int64 start, long length)
//{ int s;
// int l;
// s = tell(fh);
// if (s!=start) lseek(fh, start, SEEK_SET);
// l = locking(fh, _LK_NBLCK, length);
// if (s!=start) lseek(fh, s, SEEK_SET);
// return l;
//}
//static int qunlock(int fh, __int64 start, long length)
//{ int s;
// int l;
// s = tell(fh);
// if (s!=start) lseek(fh, start, SEEK_SET);
// l = locking(fh, _LK_UNLCK, length);
// if (s!=start) lseek(fh, s, SEEK_SET);
// return l;
//}
void write_dbheader(struct dbase_header *th)
/* write new header (recordcount) */
{ lseek (th->filehandle,0,SEEK_SET); /* seek to file start */
write(th->filehandle,&th->junk1,32); /* write the header */
th->currentrecord=-1; /* force new seek for next access */
}
void read_dbheader(struct dbase_header *th)
/* read file header (recordcount) */
{ lseek(th->filehandle,0,SEEK_SET); /* seek to file start */
read(th->filehandle,&th->junk1,32); /* and read the header */
if (th->oldupdatecount) /* auto-move the updatecount field */
{ th->updatecount = th->oldupdatecount; /* for DBASEIII compatibility */
th->oldupdatecount = 0;
}
th->currentrecord=-1; /* force new seek for next access */
}
#ifndef localtime_r
#define localtime_r(a, b) memcpy(b, localtime(a), sizeof(struct tm))
#endif
void set_dbdate(struct dbase_header *th)
/* put current date in file header */
{
time_t time_of_day;
struct tm tmbuf;
time_of_day = time(NULL);
localtime_r(&time_of_day, &tmbuf);
th->junk1[1] = tmbuf.tm_year%100; /* put in the header */
th->junk1[2] = tmbuf.tm_mon+1;
th->junk1[3] = tmbuf.tm_mday;
}
/* lock_dbheader : locks database header;
returns : 0=OK, 1=no network, 2,3=error
*/
static unsigned int seedp = 0x55555555;
#ifndef rand_r
#define rand_r(a) rand()
#endif
/* lock file header only */
int lock_dbheader(struct dbase_header *th)
{ int i;
/* already locked or not shared */
if ((th->lockcount!=0 && th->lockstart==0) || !th->sharemode) return 1;
for (i=0; i<lock_retry_count; i++)
{ if (qlock(th->filename,0,1/*th->headerlength*/)==0) return 0;
/* lock succeeded */
if (i!=lock_retry_count-1) delay(rand_r(&seedp)%lock_retry_delay);
/* wait a while and retry */
}
UNUSED_ARGUMENT(seedp);//For unused variable gcc warning
return 3; /* failed after retries */
}
void unlock_dbheader(struct dbase_header *th)
{ if ((th->lockcount!=0 && th->lockstart==0) || !th->sharemode) return;
qunlock(th->filename,0,1/*th->headerlength*/);
}
int dbase_lock(struct dbase_header *th, int firstrec, int reccount, int retries);
/*************** open a dbase file for read and or write ***************/
/* shared open (will also retry 10 s)
Returns 0, returns -errorcode on failure.
Will retry each 500 ms for 20 times if 'access denied'. If 'access denied'
the routine will not try to use another dbase_path entry.
mvh, 16-12-91: do NOT set handle pointer before complete success !
*/
#define ER_INARGUMENTS 1
#define ER_NOSUCHFILE 2
#define ER_NOSUCHFIELD 3
#define ER_ONCREATINGFILE 4
#define ER_ONDBASETRUNC 5
#define ER_FAULTYHANDLE 6
#define ER_SRCEQUALSDEST 7
#define ER_DBLOCKFAILED 8
#define ER_INVPACK1 9
#define ER_INVPACK2 10
#define ER_OUTOFHANDLES 11
#define ER_TOOMANYFIELDS 12
#define ER_TOOMANYFILES 13
#define ER_NOACCESS 14
#define ER_OPEN 15
#define ER_INVALIDHANDLE 16
#define ER_INVALIDRECORD 17
#define ER_HEADERREAD 18
#define ER_HEADERWRITE 19
#define ER_RECORDREAD 20
#define ER_RECORDWRITE 21
#define ER_SHAREDTRUNC 22
#define ER_SHAREDPACK 23
#define ER_SHAREDEXTRACT 24
#define ER_SHAREDSORT 25
#define ER_SORTTOOLARGE 26
#define ER_OPENOUTPUTFILE 27
#define ER_WRITETOREADONLY 28
#define ER_FILEALREADYOPEN 29
#define ER_INVALIDFORMAT 30
#define ER_OUTOFMEMORY 31
#define ER_INTERNAL 32
int dbase_open(char *filename, struct dbase_header **th)
{ int i,j,h,l,rret;
int errcond, rdonly;
char string[1024], blank[] = "", *p;
int mode=1;
strcpy(string,filename); /* search path */
p=blank;
errcond = file_lock_error; /* DBUSE dos error = -1 */
if (mode)
errcond = file_access_error; /* DBSHARE dos error = 5 */
rdonly = 0;
while (1)
{ for (i=0; i<open_retry_count; i++) /* retry some times */
{ if (!mode)
h = sopen(string, O_RDWR + O_BINARY + O_LARGEFILE, SH_DENYRW, 0);
else
h = sopen(string, O_RDWR + O_BINARY + O_LARGEFILE, SH_DENYNO, 0);
/* OK / other error 'no access' */
if (h != -1 ) break;
//|| (h == -1 &&
// (int)_doserrno!=errcond &&
// (int)_doserrno!=ERROR_SHARING_VIOLATION
// )) break;
if (i!=open_retry_count-1) delay(rand_r(&seedp)%open_retry_delay);
}
/* break path search if : found/path empty/path exhausted/access denied */
if ( h != -1 || p==NULL || *p==0 ) break;
//||
// (h == -1 && ((int)_doserrno==errcond ||
// (int)_doserrno==ERROR_SHARING_VIOLATION)))
// break;
strcpy(string,p); /* get new path entry */
l = strcspn(p,";");
if (string[l-1] != PATHSEPCHAR) string[l] = PATHSEPCHAR;
string[l+1] = 0;
strcat(string,filename);
p += l+1;
}
/* try once to open read only (without path) if read/write open failed (useful for CDROM access) */
if (h == -1)
{ strcpy(string, filename);
if (!mode)
h = sopen(string, O_RDONLY+ O_BINARY+ O_LARGEFILE, SH_DENYRW, 0);
else
h = sopen(string, O_RDONLY+ O_BINARY+ O_LARGEFILE, SH_DENYNO, 0);
if (h != -1) rdonly = 1;
}
if (h == -1)
{ switch (_errno) /* return failure */
{ case ENOENT : return -ER_NOSUCHFILE;
case EMFILE : return -ER_TOOMANYFILES;
case EACCES : return -ER_NOACCESS;
#if defined(__WATCOMC__) && defined(WIN32)
case -1: switch (_doserrno)
{ case ERROR_SHARING_VIOLATION:
return -ER_NOACCESS;
}
/* else fall through */
#endif /* __WATCOMC__ && WIN32 */
default : perror("dbase_open"); return -ER_OPEN;
}
}
/* must allocate also for extra header space before junk1 */
if (((*th)=(struct dbase_header *)malloc(sizeof(struct dbase_header)+32*MAXFIELDS+512))==NULL)
{ close (h);
return -ER_OUTOFMEMORY;
}
strcpy((*th)->filename, string);
/* read and process the header */
if (mode)
{ (*th)->filehandle = h; /* assume some header to lock */
(*th)->currentrecord=-1;
(*th)->bufrecord =-1;
(*th)->rawrecord =-1;
(*th)->recordlength = 1;
(*th)->headerlength =32; /* first lock 'raw' header only */
(*th)->touched = 0;
(*th)->fieldcount = 1;
(*th)->lockstart = 0;
(*th)->lockcount = 0;
(*th)->sharemode = 1;
if (lock_dbheader((*th)) > 0) /* lock header with 1 sec. retry */
mode=0; /* if no network, disable locking */
}
rret=read(h,&(*th)->junk1,32); /* read 'raw' header */
if ((*th)->oldupdatecount) /* auto-move the updatecount field */
{ (*th)->updatecount = (*th)->oldupdatecount; /* for DBASEIII compatibility */
(*th)->oldupdatecount = 0;
}
if (mode) unlock_dbheader((*th));
if (rret != 32) /* failed to read 'raw' header ? */
{
free((*th)); close(h); return -ER_HEADERREAD;
}
if ((*th)->hlength>32*MAXFIELDS+32 || (*th)->hlength<64) /* DBASE IV etc has much larger header */
{ free((*th)); close(h); return -ER_INVALIDFORMAT; }
(*th)->headerlength = (*th)->hlength; /* read rest of the header */
if (mode) lock_dbheader((*th));
rret = read(h, &(*th)->fd, (*th)->hlength-32);
if (mode) unlock_dbheader((*th));
(*th)->recordlength=1; /* count fields and recordsize */
for(j=0; j<MAXFIELDS; j++)
{ if ((*th)->fd[j].name[0] == 0x0d) break;
(*th)->recordlength += (*th)->fd[j].len;
}
if (j==MAXFIELDS) /* too many fields --> fail */
{ free((*th)); close(h); return -ER_TOOMANYFIELDS; }
if (rret != (*th)->hlength-32) /* failed to read complete header ? */
{ free((*th)); close(h); return -ER_HEADERREAD; }
/* allocate record buffers */
(*th)->recordbuffer = (char *)malloc(2 * (*th)->recordlength + j + 2);
(*th)->rawbuffer = (*th)->recordbuffer + (*th)->recordlength + j + 1;
if ((*th)->recordbuffer == NULL)
{ free((*th)); close(h); return -ER_OUTOFMEMORY; }
(*th)->fieldcount = j; /* fill in the dbase handle */
(*th)->filehandle = h;
(*th)->currentrecord= 0;
(*th)->bufrecord = -1;
(*th)->rawrecord = -1;
(*th)->headerlength = (j+1)*32+1;
(*th)->touched = 0;
(*th)->lockstart = 0; /* file not locked at start */
(*th)->lockcount = 0;
(*th)->sharemode = mode;
(*th)->readonly = rdonly;
return 0; /* and return OK */
}
/* Note : will lock whole file shortly in shared mode when touched.
The routine must be careful to read fresh file length before the
file is closed and a trailing ctrl-z is added.
update, 15-12-91, mvh: just like write, lock only when has been touched
*/
void dbase_close(struct dbase_header *th)
{ char c;
/* update header if file changed */
if (th->touched)
{ if (th->sharemode) /* lock whole file if shared */
{ dbase_lock(th,-1,0x7fffffff,lock_retry_count);
read_dbheader(th); /* get latest record count */
}
set_dbdate(th);
write_dbheader(th);
/* and add trailing ctrl-z */
LongerSeek( th->filehandle,
th->reccount, th->recordlength, th->headerlength
);
c=0x1a; write(th->filehandle,&c,1);
if (th->sharemode) /* unlock the file */
dbase_lock(th,0,0,lock_retry_count);
}
if (th->lockcount) /* free 'hanging' locks */
dbase_lock(th,0,0,lock_retry_count);
close(th->filehandle);
free(th->recordbuffer);
free(th);
}
/*************** create a dbase file for read and or write ***************/
/*
The **fielddata array should point to strings with the
following structure (last pointer should be NULL) :
NAME:Cnn for nn (decimal) length character field
NAME:Nnn for nn (decimal) length numerical field
NAME:L logical field (one byte)
NAME:D date field (8 bytes in form YYYYMMDD).
NAME may be followed by spaces.
Returns 0, returns -errorcode on failure.
*/
int dbase_create(char *filename, char **fielddata, struct dbase_header **th)
{ int i,j,h,len;
char name[20], *p, type;
h = creat(filename, S_IWRITE | S_IREAD);
if (h == -1) return -ER_ONCREATINGFILE; /* return creation failure */
close(h);
h = open(filename, O_RDWR | O_BINARY | O_LARGEFILE, 0);
/* must allocate also for extra header space before junk1 */
if (((*th)=(struct dbase_header *)malloc(sizeof(struct dbase_header)+32*MAXFIELDS+512))==NULL)
{ close(h);
return -ER_OUTOFMEMORY;
}
(*th)->recordlength=1; /* count fields and recordsize */
for(j=0; j<MAXFIELDS; j++) /* fill in the field data */
{ if (fielddata[j]==NULL) break;
p = strchr(fielddata[j], ':'); /* split in name, type and length */
if (p==NULL) /* no : found, use default type/len */
{ len = 1;
type = 'C';
}
else /* get type and length if necessary */
{ *p++ = 0;
while (isspace(*p) && *p!=0) p++;
type = toupper(*p);
if (type == 0) type = 'L'; /* when no type specified, use 'L' */
if (type == 'L') len = 1; /* get lenght or use default */
else if (type == 'D') len = 8;
else len = atoi(p+1);
}
strupr(fielddata[j]); /* polish up the name a bit */
sprintf(name,"%-10.10s",fielddata[j]);
for (i=9; i>=0; i--) if (isspace(name[i])) name[i]=0;
memcpy ((*th)->fd[j].name,name,11); /* fill in the field data */
memset ((*th)->fd[j].junk1,0,4);
memset ((*th)->fd[j].junk2,0,14);
(*th)->fd[j].type = type;
(*th)->fd[j].len = len;
(*th)->fd[j].deccount = 0;
(*th)->recordlength += (*th)->fd[j].len; /* and count the total recordlength */
}
if (j==MAXFIELDS) /* too many fields --> fail */
{ free((*th));
close(h);
unlink(filename);
return -ER_TOOMANYFIELDS;
}
(*th)->fd[j].name[0] = 0x0d; /* place a field terminator */
memset((*th)->junk2, 0, 20); /* fill in the dbase handle */
(*th)->fieldcount = j;
(*th)->filehandle = h;
(*th)->currentrecord = 0;
(*th)->bufrecord = -1;
(*th)->rawrecord = -1;
(*th)->reccount = 0;
(*th)->updatecount = 0;
(*th)->oldupdatecount= 0;
(*th)->headerlength = (j+1)*32+1;
(*th)->hlength = (*th)->headerlength;
(*th)->rlength = (*th)->recordlength;
(*th)->junk1[0] = 0x03; /* dbase III file identifier */
(*th)->touched = 1;
(*th)->lockstart = 0; /* file not locked at start */
(*th)->lockcount = 0;
(*th)->sharemode = 0;
(*th)->readonly = 0;
#if 1 /* set filename for check double opens */
strcpy((*th)->filename, filename);
#endif
/* allocate record buffer */
(*th)->recordbuffer= (char *)malloc(2 * (*th)->recordlength + (*th)->fieldcount + 2);
(*th)->rawbuffer = (*th)->recordbuffer + (*th)->recordlength + (*th)->fieldcount + 1;
if ((*th)->recordbuffer == NULL)
{ free((*th));
close(h);
unlink(filename);
return -ER_OUTOFMEMORY;
}
set_dbdate((*th));
/* write the fresh header */
i = write((*th)->filehandle,&(*th)->junk1,(*th)->headerlength);
if (i != (*th)->headerlength) /* write failed ? */
{ free((*th)->recordbuffer);
free((*th));
close(h);
unlink(filename);
return -ER_HEADERWRITE;
}
lseek(h, (*th)->headerlength, SEEK_SET); /* rewind file to record 0 */
dbase_close((*th));
dbase_open(filename, th);
return 0; /* and return ok */
}
/************************ record locking operator **************************/
/* Parameters : handle = dbase handle of file to lock
firstrec = first record to lock, use -1 to lock header also
reccount = number of records to lock (use 0 to unlock file)
retries = number of 100 ms retries before failure
Returns : 0 : locked O.K.
1 : no network/multitasking locking function available
2 : error
3 : lock failed after some retries
Note : Locking a new file part automatically frees previous lock.
*/
int dbase_lock(struct dbase_header *th, int firstrec, int reccount, int retries)
{ int i,j;
int locks;
int lockc;
/********** Check file handle *********/
if (!th->sharemode) return 1; /* no locking on normal file */