1 /***************************************************************************
2 tqslconvert.cpp - description
3 -------------------
4 begin : Sun Nov 17 2002
5 copyright : (C) 2002 by ARRL
6 author : Jon Bloom
7 email : jbloom@arrl.org
8 revision : $Id$
9 ***************************************************************************/
10
11
12 #define TQSLLIB_DEF
13
14 #include "tqsllib.h"
15
16 #include "tqslconvert.h"
17 #include <stdio.h>
18 #include <errno.h>
19 #include <sys/stat.h>
20 #include <signal.h>
21 #include "tqslerrno.h"
22 #include <cstring>
23 #include <string>
24 #include <ctype.h>
25 #include <set>
26 #ifdef USE_LMDB
27 #include <lmdb.h>
28 #define db_strerror mdb_strerror
29 #else
30 #include <db.h>
31 #endif
32
33 #include <locale.h>
34 //#include <iostream>
35
36 #ifndef _WIN32
37 #include <unistd.h>
38 #include <dirent.h>
39 #else
40 #include <direct.h>
41 #include "windirent.h"
42 #endif
43
44 #include "winstrdefs.h"
45
46 using std::set;
47 using std::string;
48
49 static bool checkCallSign(const string& call);
50
51 namespace tqsllib {
52
53 class TQSL_CONVERTER {
54 public:
55 TQSL_CONVERTER();
56 ~TQSL_CONVERTER();
57 void clearRec();
58 int sentinel;
59 // FILE *file;
60 tQSL_ADIF adif;
61 tQSL_Cabrillo cab;
62 tQSL_Cert *certs;
63 int ncerts;
64 tQSL_Location loc;
65 TQSL_QSO_RECORD rec;
66 bool rec_done;
67 int cert_idx;
68 int base_idx;
69 bool need_station_rec;
70 bool *certs_used;
71 bool allow_bad_calls;
72 set <string> modes;
73 set <string> bands;
74 set <string> propmodes;
75 set <string> satellites;
76 string rec_text;
77 tQSL_Date start, end;
78 bool db_open;
79 #ifdef USE_LMDB
80 MDB_dbi seendb;
81 MDB_env* dbenv;
82 MDB_txn* txn;
83 MDB_cursor* cursor;
84 #else
85 DB *seendb;
86 DB_ENV* dbenv;
87 DB_TXN* txn;
88 DBC* cursor;
89 #endif
90 char *dbpath;
91 FILE* errfile;
92 char serial[512];
93 char callsign[64];
94 bool allow_dupes;
95 bool need_ident_rec;
96 char *appName;
97 };
98
TQSL_CONVERTER()99 inline TQSL_CONVERTER::TQSL_CONVERTER() : sentinel(0x4445) {
100 // file = 0;
101 adif = 0;
102 cab = 0;
103 cert_idx = -1;
104 base_idx = 1;
105 certs_used = 0;
106 need_station_rec = false;
107 rec_done = true;
108 allow_bad_calls = false;
109 allow_dupes = true; //by default, don't change existing behavior (also helps with commit)
110 memset(&rec, 0, sizeof rec);
111 memset(&start, 0, sizeof start);
112 memset(&end, 0, sizeof end);
113 db_open = false;
114 #ifndef USE_LMDB
115 seendb = NULL;
116 #endif
117 dbpath = NULL;
118 dbenv = NULL;
119 txn = NULL;
120 cursor = NULL;
121 errfile = NULL;
122 memset(&serial, 0, sizeof serial);
123 memset(&callsign, 0, sizeof callsign);
124 appName = NULL;
125 need_ident_rec = true;
126 // Init the band data
127 const char *val;
128 int n = 0;
129 tqsl_getNumBand(&n);
130 for (int i = 0; i < n; i++) {
131 val = 0;
132 tqsl_getBand(i, &val, 0, 0, 0);
133 if (val)
134 bands.insert(val);
135 }
136 // Init the mode data
137 n = 0;
138 tqsl_getNumMode(&n);
139 for (int i = 0; i < n; i++) {
140 val = 0;
141 tqsl_getMode(i, &val, 0);
142 if (val)
143 modes.insert(val);
144 }
145 // Init the propagation mode data
146 n = 0;
147 tqsl_getNumPropagationMode(&n);
148 for (int i = 0; i < n; i++) {
149 val = 0;
150 tqsl_getPropagationMode(i, &val, 0);
151 if (val)
152 propmodes.insert(val);
153 }
154 // Init the satellite data
155 n = 0;
156 tqsl_getNumSatellite(&n);
157 for (int i = 0; i < n; i++) {
158 val = 0;
159 tqsl_getSatellite(i, &val, 0, 0, 0);
160 if (val)
161 satellites.insert(val);
162 }
163 }
164
~TQSL_CONVERTER()165 inline TQSL_CONVERTER::~TQSL_CONVERTER() {
166 clearRec();
167 // if (file)
168 // fclose(file);
169 tqsl_endADIF(&adif);
170 if (certs_used)
171 delete[] certs_used;
172 sentinel = 0;
173 }
174
clearRec()175 inline void TQSL_CONVERTER::clearRec() {
176 memset(&rec, 0, sizeof rec);
177 rec_text = "";
178 }
179
180 #define CAST_TQSL_CONVERTER(x) ((tqsllib::TQSL_CONVERTER *)(x))
181
182 } // namespace tqsllib
183
184 using tqsllib::TQSL_CONVERTER;
185
fix_freq(const char * in)186 static char * fix_freq(const char *in) {
187 static char out[128];
188 const char *p = in;
189 bool decimal = false;
190 char *o = out;
191 while (*p) {
192 if (*p == '.') {
193 if (decimal) {
194 p++;
195 continue;
196 }
197 decimal = true;
198 }
199 *o++ = *p++;
200 }
201 *o = '\0';
202 return out;
203 }
204
205 static char *
tqsl_strtoupper(char * str)206 tqsl_strtoupper(char *str) {
207 for (char *cp = str; *cp != '\0'; cp++)
208 *cp = toupper(*cp);
209 return str;
210 }
211
212 static TQSL_CONVERTER *
check_conv(tQSL_Converter conv)213 check_conv(tQSL_Converter conv) {
214 if (tqsl_init())
215 return 0;
216 if (conv == 0 || CAST_TQSL_CONVERTER(conv)->sentinel != 0x4445)
217 return 0;
218 return CAST_TQSL_CONVERTER(conv);
219 }
220
221 static tqsl_adifFieldDefinitions adif_qso_record_fields[] = {
222 { "CALL", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_CALLSIGN_MAX, 0, 0, NULL },
223 { "BAND", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, NULL },
224 { "MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_MODE_MAX, 0, 0, NULL },
225 { "SUBMODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_MODE_MAX, 0, 0, NULL },
226 { "QSO_DATE", "", TQSL_ADIF_RANGE_TYPE_NONE, 10, 0, 0, NULL },
227 { "TIME_ON", "", TQSL_ADIF_RANGE_TYPE_NONE, 10, 0, 0, NULL },
228 { "FREQ", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, NULL },
229 { "FREQ_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_FREQ_MAX, 0, 0, NULL },
230 { "BAND_RX", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_BAND_MAX, 0, 0, NULL },
231 { "SAT_NAME", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_SATNAME_MAX, 0, 0, NULL },
232 { "PROP_MODE", "", TQSL_ADIF_RANGE_TYPE_NONE, TQSL_PROPMODE_MAX, 0, 0, NULL },
233 { "eor", "", TQSL_ADIF_RANGE_TYPE_NONE, 0, 0, 0, NULL },
234 };
235
236 DLLEXPORT int CALLCONVENTION
tqsl_beginConverter(tQSL_Converter * convp)237 tqsl_beginConverter(tQSL_Converter *convp) {
238 tqslTrace("tqsl_beginConverter", NULL);
239 if (tqsl_init())
240 return 0;
241 if (!convp) {
242 tqslTrace("tqsl_beginConverter", "convp=NULL");
243 tQSL_Error = TQSL_ARGUMENT_ERROR;
244 return 1;
245 }
246 TQSL_CONVERTER *conv = new TQSL_CONVERTER();
247 *convp = conv;
248 return 0;
249 }
250
251 DLLEXPORT int CALLCONVENTION
tqsl_beginADIFConverter(tQSL_Converter * convp,const char * filename,tQSL_Cert * certs,int ncerts,tQSL_Location loc)252 tqsl_beginADIFConverter(tQSL_Converter *convp, const char *filename, tQSL_Cert *certs,
253 int ncerts, tQSL_Location loc) {
254 tqslTrace("tqsl_beginADIFConverter", NULL);
255 if (tqsl_init())
256 return 0;
257 if (!convp || !filename) {
258 tqslTrace("tqsl_beginADIFConverter", "arg err convp=0x%lx filename=0x%lx certs=0x%lx", convp, filename, certs);
259 tQSL_Error = TQSL_ARGUMENT_ERROR;
260 return 1;
261 }
262 tQSL_ADIF adif;
263 if (tqsl_beginADIF(&adif, filename)) {
264 tqslTrace("tqsl_beginADIFConverter", "tqsl_beginADIF fail %d", tQSL_Error);
265 return 1;
266 }
267 TQSL_CONVERTER *conv = new TQSL_CONVERTER();
268 conv->adif = adif;
269 conv->certs = certs;
270 conv->ncerts = ncerts;
271 if (ncerts > 0) {
272 conv->certs_used = new bool[ncerts];
273 for (int i = 0; i < ncerts; i++)
274 conv->certs_used[i] = false;
275 }
276 conv->loc = loc;
277 *convp = conv;
278 return 0;
279 }
280
281 DLLEXPORT int CALLCONVENTION
tqsl_beginCabrilloConverter(tQSL_Converter * convp,const char * filename,tQSL_Cert * certs,int ncerts,tQSL_Location loc)282 tqsl_beginCabrilloConverter(tQSL_Converter *convp, const char *filename, tQSL_Cert *certs,
283 int ncerts, tQSL_Location loc) {
284 tqslTrace("tqsl_beginCabrilloConverter", NULL);
285
286 if (tqsl_init())
287 return 0;
288 if (!convp || !filename) {
289 tQSL_Error = TQSL_ARGUMENT_ERROR;
290 tqslTrace("tqsl_beginCabrilloConverter", "arg error convp=0x%lx, filename=0x%lx, certs=0x%lx", convp, filename, certs);
291 return 1;
292 }
293 tQSL_Cabrillo cab;
294 if (tqsl_beginCabrillo(&cab, filename)) {
295 tqslTrace("tqsl_beginCabrilloConverter", "tqsl_beginCabrillo fail %d", tQSL_Error);
296 return 1;
297 }
298 TQSL_CONVERTER *conv = new TQSL_CONVERTER();
299 conv->cab = cab;
300 conv->certs = certs;
301 conv->ncerts = ncerts;
302 if (ncerts > 0) {
303 conv->certs_used = new bool[ncerts];
304 for (int i = 0; i < ncerts; i++)
305 conv->certs_used[i] = false;
306 }
307 conv->loc = loc;
308 *convp = conv;
309 return 0;
310 }
311
312 DLLEXPORT int CALLCONVENTION
tqsl_endConverter(tQSL_Converter * convp)313 tqsl_endConverter(tQSL_Converter *convp) {
314 tqslTrace("tqsl_endConverter", NULL);
315
316 if (!convp || CAST_TQSL_CONVERTER(*convp) == 0)
317 return 0;
318
319 TQSL_CONVERTER* conv;
320
321 if ((conv = check_conv(*convp))) {
322 #ifdef USE_LMDB
323 if (conv->txn) mdb_txn_abort(conv->txn);
324 #else
325 if (conv->txn) conv->txn->abort(conv->txn);
326 #endif
327 if (conv->db_open) {
328 #ifdef USE_LMDB
329 mdb_dbi_close(conv->dbenv, conv->seendb);
330 #else
331 conv->seendb->compact(conv->seendb, NULL, NULL, NULL, NULL, 0, NULL);
332 conv->seendb->close(conv->seendb, 0);
333 #endif
334 }
335 conv->db_open = false;
336 if (conv->dbenv) {
337 #ifdef USE_LMDB
338 mdb_env_close(conv->dbenv);
339 #else
340 char **unused;
341 conv->dbenv->txn_checkpoint(conv->dbenv, 0, 0, 0);
342 conv->dbenv->log_archive(conv->dbenv, &unused, DB_ARCH_REMOVE);
343 conv->dbenv->close(conv->dbenv, 0);
344 #endif
345 }
346 // close files and clean up converters, if any
347 if (conv->adif) tqsl_endADIF(&conv->adif);
348 if (conv->cab) tqsl_endCabrillo(&conv->cab);
349 if (conv->dbpath) free(conv->dbpath);
350 if (conv->errfile) fclose(conv->errfile);
351 }
352
353 if (conv->appName) free(conv->appName);
354 if (CAST_TQSL_CONVERTER(*convp)->sentinel == 0x4445)
355 delete CAST_TQSL_CONVERTER(*convp);
356 *convp = 0;
357 return 0;
358 }
359
360 static unsigned char *
adif_allocate(size_t size)361 adif_allocate(size_t size) {
362 return new unsigned char[size];
363 }
364
365 static int
find_matching_cert(TQSL_CONVERTER * conv)366 find_matching_cert(TQSL_CONVERTER *conv) {
367 int i;
368 for (i = 0; i < conv->ncerts; i++) {
369 tQSL_Date cdate;
370
371 if (tqsl_getCertificateQSONotBeforeDate(conv->certs[i], &cdate))
372 return -1;
373 if (tqsl_compareDates(&(conv->rec.date), &cdate) < 0)
374 continue;
375 if (tqsl_getCertificateQSONotAfterDate(conv->certs[i], &cdate))
376 return -1;
377 if (tqsl_compareDates(&(conv->rec.date), &cdate) > 0)
378 continue;
379 return i;
380 }
381 return -1;
382 }
383
384 static const char *notypes[] = { "D", "T", "M", "N", "C", "" };
385
386 static const char *
tqsl_infer_band(const char * infreq)387 tqsl_infer_band(const char* infreq) {
388 char *oldlocale = setlocale(LC_NUMERIC, "C");
389 double freq = atof(infreq);
390 setlocale(LC_NUMERIC, oldlocale);
391 double freq_khz = freq * 1000.0;
392 int nband = 0;
393 tqsl_getNumBand(&nband);
394 for (int i = 0; i < nband; i++) {
395 const char *name;
396 const char *spectrum;
397 int low, high;
398 if (tqsl_getBand(i, &name, &spectrum, &low, &high))
399 break;
400 bool match = false;
401 if (!strcmp(spectrum, "HF")) {
402 // Allow for cases where loggers that don't log the
403 // real frequency.
404 if (low == 10100) low = 10000;
405 else if (low == 18068) low = 18000;
406 else if (low == 24890) low = 24000;
407 if (freq_khz >= low && freq_khz <= high) {
408 match = true;
409 }
410 } else {
411 if (freq >= low && freq <= high)
412 match = true;
413 if (freq >= low && high == 0)
414 match = true;
415 }
416 if (match)
417 return name;
418 }
419 return "";
420 }
421
422 DLLEXPORT int CALLCONVENTION
tqsl_setADIFConverterDateFilter(tQSL_Converter convp,tQSL_Date * start,tQSL_Date * end)423 tqsl_setADIFConverterDateFilter(tQSL_Converter convp, tQSL_Date *start, tQSL_Date *end) {
424 TQSL_CONVERTER *conv;
425 tqslTrace("tqsl_setADIFConverterDateFilter", NULL);
426
427 if (!(conv = check_conv(convp)))
428 return 1;
429 if (start == NULL)
430 conv->start.year = conv->start.month = conv->start.day = 0;
431 else
432 conv->start = *start;
433 if (end == NULL)
434 conv->end.year = conv->end.month = conv->end.day = 0;
435 else
436 conv->end = *end;
437 return 0;
438 }
439
440 // Remove the dupes db files
441 static void
remove_db(const char * path)442 remove_db(const char *path) {
443 tqslTrace("remove_db", "path=%s", path);
444 #ifdef _WIN32
445 wchar_t* wpath = utf8_to_wchar(path);
446 _WDIR *dir = _wopendir(wpath);
447 free_wchar(wpath);
448 #else
449 DIR *dir = opendir(path);
450 #endif
451 if (dir != NULL) {
452 #ifdef USE_LMDB
453 #ifdef _WIN32
454 struct _wdirent *ent = NULL;
455 while ((ent = _wreaddir(dir)) != NULL) {
456 if (!wcscmp(ent->d_name, L"data.mdb") ||
457 !wcscmp(ent->d_name, L"lock.mdb")) {
458 #else
459 struct dirent *ent = NULL;
460 while ((ent = readdir(dir)) != NULL) {
461 if (!strcmp(ent->d_name, "data.mdb") ||
462 !strcmp(ent->d_name, "lock.mdb")) {
463 #endif
464 #else // USE_LMDB
465 #ifdef _WIN32
466 struct _wdirent *ent = NULL;
467 while ((ent = _wreaddir(dir)) != NULL) {
468 if (!wcscmp(ent->d_name, L"duplicates.db") ||
469 !wcsncmp(ent->d_name, L"log.", 4) ||
470 !wcsncmp(ent->d_name, L"__db.", 5)) {
471 #else
472 struct dirent *ent = NULL;
473 while ((ent = readdir(dir)) != NULL) {
474 if (!strcmp(ent->d_name, "duplicates.db") ||
475 !strncmp(ent->d_name, "log.", 4) ||
476 !strncmp(ent->d_name, "__db.", 5)) {
477 #endif
478 #endif // USE_LMDB
479 string fname = path;
480 int rstat;
481 #ifdef _WIN32
482 char dname[TQSL_MAX_PATH_LEN];
483 wcstombs(dname, ent->d_name, TQSL_MAX_PATH_LEN);
484 fname = fname + "/" + dname;
485 wchar_t* wfname = utf8_to_wchar(fname.c_str());
486 tqslTrace("remove_db", "unlinking %s", fname.c_str());
487 rstat = _wunlink(wfname);
488 free_wchar(wfname);
489 #else
490 fname = fname + "/" + ent->d_name;
491 tqslTrace("remove_db", "unlinking %s", fname.c_str());
492 rstat = unlink(fname.c_str());
493 #endif
494 if (rstat < 0) {
495 tqslTrace("remove_db", "can't unlink %s: %s", fname.c_str(), strerror(errno));
496 }
497 }
498 }
499 #ifdef _WIN32
500 _wclosedir(dir);
501 #else
502 closedir(dir);
503 #endif
504 }
505 return;
506 }
507 #if !defined(_WIN32) && !defined(USE_LMDB)
508 // Callback method for the dbenv->failchk() call
509 // Used to determine if the given pid/tid is
510 // alive.
511 static int isalive(DB_ENV *env, pid_t pid, db_threadid_t tid, uint32_t flags) {
512 int alive = 0;
513
514 if (pid == getpid()) {
515 alive = 1;
516 } else if (kill(pid, 0) == 0) {
517 alive = 1;
518 } else if (errno == EPERM) {
519 alive = 1;
520 }
521 return alive;
522 }
523 #endif // _WIN32
524
525 // Open the duplicates database
526
527 #ifdef USE_LMDB
528 static bool open_db(TQSL_CONVERTER *conv, bool readonly) {
529 bool dbinit_cleanup = false;
530 int dbret;
531 bool triedRemove = false;
532 bool triedDelete = false;
533 string fixedpath = tQSL_BaseDir; //must be first because of gotos
534 size_t found = fixedpath.find('\\');
535
536 tqslTrace("open_db", "path=%s", fixedpath.c_str());
537 //bdb complains about \\s in path on windows...
538
539 while (found != string::npos) {
540 fixedpath.replace(found, 1, "/");
541 found = fixedpath.find('\\');
542 }
543
544 conv->dbpath = strdup(fixedpath.c_str());
545
546 #ifndef _WIN32
547 // Clean up junk in that directory
548 DIR *dir = opendir(fixedpath.c_str());
549 if (dir != NULL) {
550 struct dirent *ent;
551 while ((ent = readdir(dir)) != NULL) {
552 if (ent->d_name[0] == '.')
553 continue;
554 struct stat s;
555 // If it's a symlink pointing to itself, remove it.
556 string fname = fixedpath + "/" + ent->d_name;
557 if (stat(fname.c_str(), &s)) {
558 if (errno == ELOOP) {
559 #ifdef _WIN32
560 _wunlink(ConvertFromUtf8ToUtf16(fname.c_str()));
561 #else
562 unlink(fname.c_str());
563 #endif
564 }
565 }
566 }
567 closedir(dir);
568 }
569 #endif
570 string logpath = fixedpath + "/dberr.log";
571 #ifdef _WIN32
572 wchar_t* wlogpath = utf8_to_wchar(logpath.c_str());
573 conv->errfile = _wfopen(wlogpath, L"wb");
574 free_wchar(wlogpath);
575 #else
576 conv->errfile = fopen(logpath.c_str(), "wb");
577 #endif
578
579 reopen:
580
581 // Try to open the database
582 while (true) {
583 if (!conv->dbenv) {
584 // Create the database environment handle
585 if ((dbret = mdb_env_create(&conv->dbenv))) {
586 // can't make env handle
587 tqslTrace("open_db", "mdb_env_create error %s", mdb_strerror(dbret));
588 if (conv->errfile)
589 fprintf(conv->errfile, "mdb_env_create error %s\n", mdb_strerror(dbret));
590 dbinit_cleanup = true;
591 goto dbinit_end;
592 }
593 tqslTrace("open_db", "dbenv=0x%lx", conv->dbenv);
594 }
595 mdb_env_set_maxdbs(conv->dbenv, 2);
596 mdb_env_set_maxreaders(conv->dbenv, 2);
597 mdb_env_set_mapsize(conv->dbenv, 1024 * 1024 * 1024);
598 // Now open the database
599 tqslTrace("open_db", "Opening the database at %s", conv->dbpath);
600 if ((dbret = mdb_env_open(conv->dbenv, conv->dbpath, 0, 0600))) {
601 tqslTrace("open_db", "dbenv->open %s error %s", conv->dbpath, mdb_strerror(dbret));
602 if (conv->errfile)
603 fprintf(conv->errfile, "opening DB %s returns status %s\n", conv->dbpath, mdb_strerror(dbret));
604 // can't open environment - try to delete it and try again.
605 tqslTrace("open_db", "Environment open fail, triedRemove=%d", triedRemove);
606 if (!triedRemove) {
607 // Remove the dross
608 tqslTrace("open_db", "Removing environment");
609 conv->dbenv = NULL;
610 triedRemove = true;
611 if (conv->errfile)
612 fprintf(conv->errfile, "About to retry after removing the environment\n");
613 tqslTrace("open_db", "About to retry after removing the environment");
614 continue;
615 }
616 tqslTrace("open_db", "Retry attempt after removing the environment failed");
617 if (conv->errfile) {
618 fprintf(conv->errfile, "Retry attempt after removing the environment failed.\n");
619 }
620 // can't open environment and cleanup efforts failed.
621 mdb_env_close(conv->dbenv);
622 conv->dbenv = NULL; // this can't be recovered
623 dbinit_cleanup = true;
624 tqslTrace("open_db", "can't fix. abandoning.");
625 remove_db(fixedpath.c_str());
626 goto dbinit_end;
627 }
628 break; // Opened OK.
629 }
630
631 tqslTrace("open_db", "starting transaction, readonly=%d", readonly);
632 if ((dbret = mdb_txn_begin(conv->dbenv, NULL, readonly ? MDB_RDONLY : 0, &conv->txn))) {
633 // can't start a txn
634 tqslTrace("open_db", "can't create txn %s", mdb_strerror(dbret));
635 if (conv->errfile)
636 fprintf(conv->errfile, "Can't create transaction: %s\n", mdb_strerror(dbret));
637 dbinit_cleanup = true;
638 goto dbinit_end;
639 }
640
641 tqslTrace("open_db", "opening database now");
642 if ((dbret = mdb_dbi_open(conv->txn, NULL, 0, &conv->seendb))) {
643 if (dbret == MDB_NOTFOUND) {
644 tqslTrace("open_db", "DB not found, making a new one");
645 dbret = mdb_dbi_open(conv->txn, NULL, MDB_CREATE, &conv->seendb);
646 }
647 if (dbret) {
648 // can't open the db
649 tqslTrace("open_db", "create failed with %s errno %d", mdb_strerror(dbret), errno);
650 if (conv->errfile)
651 fprintf(conv->errfile, "create failed with %s errno %d", mdb_strerror(dbret), errno);
652 dbinit_cleanup = true;
653 goto dbinit_end;
654 }
655 }
656
657 dbinit_end:
658 if (dbinit_cleanup) {
659 tqslTrace("open_db", "DB open failed, triedDelete=%d", triedDelete);
660 tQSL_Error = TQSL_DB_ERROR;
661 tQSL_Errno = errno;
662 strncpy(tQSL_CustomError, mdb_strerror(dbret), sizeof tQSL_CustomError);
663 tqslTrace("open_db", "Error opening db: %s", tQSL_CustomError);
664 if (conv->txn) mdb_txn_abort(conv->txn);
665 conv->txn = NULL;
666 if (conv->db_open) {
667 mdb_dbi_close(conv->dbenv, conv->seendb);
668 conv->db_open = false;
669 }
670 if (conv->dbenv) {
671 if (conv->dbpath) {
672 free(conv->dbpath);
673 conv->dbpath = NULL;
674 }
675 mdb_drop(conv->txn, conv->seendb, 1);
676 mdb_env_close(conv->dbenv);
677 }
678 if (conv->cursor) mdb_cursor_close(conv->cursor);
679 if (conv->errfile) fclose(conv->errfile);
680 conv->dbenv = NULL;
681 conv->cursor = NULL;
682 conv->errfile = NULL;
683 // Handle case where the database is just broken
684 if (dbret == EINVAL && !triedDelete) {
685 tqslTrace("open_db", "EINVAL. Removing db");
686 remove_db(fixedpath.c_str());
687 triedDelete = true;
688 goto reopen;
689 }
690 conv->db_open = false;
691 return false;
692 }
693 conv->db_open = true;
694 return true;
695 }
696 #else // USE_LMDB
697 static bool open_db(TQSL_CONVERTER *conv, bool readonly) {
698 bool dbinit_cleanup = false;
699 int dbret;
700 bool triedRemove = false;
701 bool triedDelete = false;
702 int envflags = DB_INIT_TXN|DB_INIT_LOG|DB_INIT_MPOOL|DB_RECOVER|DB_REGISTER|DB_CREATE;
703 string fixedpath = tQSL_BaseDir; //must be first because of gotos
704 size_t found = fixedpath.find('\\');
705
706 tqslTrace("open_db", "path=%s", fixedpath.c_str());
707 //bdb complains about \\s in path on windows...
708
709 while (found != string::npos) {
710 fixedpath.replace(found, 1, "/");
711 found = fixedpath.find('\\');
712 }
713
714 conv->dbpath = strdup(fixedpath.c_str());
715
716 #ifndef _WIN32
717 // Clean up junk in that directory
718 DIR *dir = opendir(fixedpath.c_str());
719 if (dir != NULL) {
720 struct dirent *ent;
721 while ((ent = readdir(dir)) != NULL) {
722 if (ent->d_name[0] == '.')
723 continue;
724 struct stat s;
725 // If it's a symlink pointing to itself, remove it.
726 string fname = fixedpath + "/" + ent->d_name;
727 if (stat(fname.c_str(), &s)) {
728 if (errno == ELOOP) {
729 #ifdef _WIN32
730 _wunlink(ConvertFromUtf8ToUtf16(fname.c_str()));
731 #else
732 unlink(fname.c_str());
733 #endif
734 }
735 }
736 }
737 closedir(dir);
738 }
739 #endif
740 string logpath = fixedpath + "/dberr.log";
741 #ifdef _WIN32
742 wchar_t* wlogpath = utf8_to_wchar(logpath.c_str());
743 conv->errfile = _wfopen(wlogpath, L"wb");
744 free_wchar(wlogpath);
745 #else
746 conv->errfile = fopen(logpath.c_str(), "wb");
747 #endif
748
749 reopen:
750
751 // Try to open the database
752 while (true) {
753 if (!conv->dbenv) {
754 // Create the database environment handle
755 if ((dbret = db_env_create(&conv->dbenv, 0))) {
756 // can't make env handle
757 tqslTrace("open_db", "db_env_create error %s", db_strerror(dbret));
758 dbinit_cleanup = true;
759 goto dbinit_end;
760 }
761 tqslTrace("open_db", "dbenv=0x%lx", conv->dbenv);
762 if (conv->errfile) {
763 conv->dbenv->set_errfile(conv->dbenv, conv->errfile);
764 conv->dbenv->set_verbose(conv->dbenv, DB_VERB_RECOVERY, 1);
765 }
766 // Enable stale lock removal
767 conv->dbenv->set_thread_count(conv->dbenv, 8);
768 #ifndef _WIN32
769 conv->dbenv->set_isalive(conv->dbenv, isalive);
770 #endif
771 // Log files default to 10 Mb each. We don't need nearly that much.
772 if (conv->dbenv->set_lg_max)
773 conv->dbenv->set_lg_max(conv->dbenv, 256 * 1024);
774 // Allocate additional locking resources - some have run out with
775 // the default 1000 locks
776 if (conv->dbenv->set_lk_max_locks)
777 conv->dbenv->set_lk_max_locks(conv->dbenv, 20000);
778 if (conv->dbenv->set_lk_max_objects)
779 conv->dbenv->set_lk_max_objects(conv->dbenv, 20000);
780 }
781 // Now open the database
782 tqslTrace("open_db", "Opening the database at %s", conv->dbpath);
783 if ((dbret = conv->dbenv->open(conv->dbenv, conv->dbpath, envflags, 0600))) {
784 int db_errno = errno;
785 tqslTrace("open_db", "dbenv->open %s error %s", conv->dbpath, db_strerror(dbret));
786 if (conv->errfile)
787 fprintf(conv->errfile, "opening DB %s returns status %s\n", conv->dbpath, db_strerror(dbret));
788 // Can't open the database - maybe try private?
789 if ((dbret == EACCES || dbret == EROFS) || (dbret == EINVAL && errno == dbret)) {
790 if (!(envflags & DB_PRIVATE)) {
791 envflags |= DB_PRIVATE;
792 continue;
793 }
794 }
795 // can't open environment - try to delete it and try again.
796 tqslTrace("open_db", "Environment open fail, triedRemove=%d", triedRemove);
797 if (!triedRemove) {
798 // Remove the dross
799 tqslTrace("open_db", "Removing environment");
800 conv->dbenv->remove(conv->dbenv, conv->dbpath, DB_FORCE);
801 conv->dbenv = NULL;
802 triedRemove = true;
803 if (conv->errfile)
804 fprintf(conv->errfile, "About to retry after removing the environment\n");
805 tqslTrace("open_db", "About to retry after removing the environment");
806 continue;
807 }
808 tqslTrace("open_db", "Retry attempt after removing the environment failed");
809 if (conv->errfile) {
810 fprintf(conv->errfile, "Retry attempt after removing the environment failed.\n");
811 }
812 // EINVAL means that the database is corrupted to the point
813 // where it can't be opened. Remove it and try again.
814 if ((dbret == EINVAL || db_errno == EINVAL) && !triedDelete) {
815 tqslTrace("open_db", "EINVAL. Removing db");
816 conv->dbenv->close(conv->dbenv, 0);
817 conv->dbenv = NULL;
818 remove_db(fixedpath.c_str());
819 triedDelete = true;
820 continue;
821 }
822
823 // can't open environment and cleanup efforts failed.
824 conv->dbenv->close(conv->dbenv, 0);
825 conv->dbenv = NULL; // this can't be recovered
826 dbinit_cleanup = true;
827 tqslTrace("open_db", "can't fix. abandoning.");
828 remove_db(fixedpath.c_str());
829 goto dbinit_end;
830 }
831 break; // Opened OK.
832 }
833
834 #ifndef _WIN32 // isalive() method doesn't exist for WIN32.
835 // Stale lock removal
836 tqslTrace("open_db", "Removing stale locks");
837 dbret = conv->dbenv->failchk(conv->dbenv, 0);
838 if (dbret && conv->errfile) {
839 fprintf(conv->errfile, "lock removal for DB %s returns status %s\n", conv->dbpath, db_strerror(dbret));
840 }
841 #endif
842
843 tqslTrace("open_db", "calling db_create");
844 if ((dbret = db_create(&conv->seendb, conv->dbenv, 0))) {
845 // can't create db
846 dbinit_cleanup = true;
847 tqslTrace("open_db", "Can't create db");
848 goto dbinit_end;
849 }
850
851 #ifndef DB_TXN_BULK
852 #define DB_TXN_BULK 0
853 #endif
854 tqslTrace("open_db", "starting transaction, readonly=%d", readonly);
855 if (!readonly && (dbret = conv->dbenv->txn_begin(conv->dbenv, NULL, &conv->txn, DB_TXN_BULK))) {
856 // can't start a txn
857 tqslTrace("open_db", "can't create txn %s", db_strerror(dbret));
858 dbinit_cleanup = true;
859 goto dbinit_end;
860 }
861
862 // Probe the database type
863 tqslTrace("open_db", "opening database now");
864 if ((dbret = conv->seendb->open(conv->seendb, conv->txn, "duplicates.db", NULL, DB_UNKNOWN, 0, 0600))) {
865 if (dbret == ENOENT) {
866 tqslTrace("open_db", "DB not found, making a new one");
867 dbret = conv->seendb->open(conv->seendb, conv->txn, "duplicates.db", NULL, DB_HASH, DB_CREATE, 0600);
868 }
869 if (dbret) {
870 // can't open the db
871 tqslTrace("open_db", "create failed with %s errno %d", db_strerror(dbret), errno);
872 dbinit_cleanup = true;
873 goto dbinit_end;
874 }
875 }
876
877 DBTYPE type;
878 conv->seendb->get_type(conv->seendb, &type);
879 tqslTrace("open_db", "type=%d", type);
880 if (type == DB_BTREE) {
881 tqslTrace("open_db", "BTREE type. Converting.");
882 // Have to convert the database.
883 string dumpfile = fixedpath + "/dupedump.txt";
884 #ifdef _WIN32
885 wchar_t* wdumpfile = utf8_to_wchar(dumpfile.c_str());
886 FILE *dmp = _wfopen(wdumpfile, L"wb+");
887 free_wchar(wdumpfile);
888 #else
889 FILE *dmp = fopen(dumpfile.c_str(), "wb+");
890 #endif
891 if (!dmp) {
892 tqslTrace("open_db", "Error opening dump file %s: %s", dumpfile.c_str(), strerror(errno));
893 dbinit_cleanup = true;
894 goto dbinit_end;
895 }
896 if (!conv->cursor) {
897 #ifndef DB_CURSOR_BULK
898 #define DB_CURSOR_BULK 0
899 #endif
900 int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
901 if (err) {
902 strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
903 tQSL_Error = TQSL_DB_ERROR;
904 tQSL_Errno = errno;
905 tqslTrace("open_db", "Error setting cursor for old DB: %s", err);
906 dbinit_cleanup = true;
907 goto dbinit_end;
908 }
909 }
910
911 DBT dbkey, dbdata;
912 char duprec[512];
913 while (1) {
914 memset(&dbkey, 0, sizeof dbkey);
915 memset(&dbdata, 0, sizeof dbdata);
916 int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
917 if (DB_NOTFOUND == status) {
918 break; // No more records
919 }
920 if (status != 0) {
921 strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
922 tQSL_Error = TQSL_DB_ERROR;
923 tQSL_Errno = errno;
924 tqslTrace("open_db", "Error reading for dump: %s", db_strerror(status));
925 dbinit_cleanup = true;
926 goto dbinit_end;
927 }
928 memcpy(duprec, dbkey.data, dbkey.size);
929 duprec[dbkey.size] = '\0';
930 fprintf(dmp, "%s\n", duprec);
931 }
932 conv->cursor->close(conv->cursor);
933 if (conv->txn) conv->txn->commit(conv->txn, 0);
934 conv->seendb->close(conv->seendb, 0);
935 conv->db_open = false;
936 conv->dbenv->remove(conv->dbenv, conv->dbpath, DB_FORCE);
937 conv->dbenv->close(conv->dbenv, 0);
938 conv->cursor = NULL;
939 conv->seendb = NULL;
940 conv->dbenv = NULL;
941
942 // Remove the old dupe db
943 tqslTrace("open_db", "Removing old format db");
944 remove_db(fixedpath.c_str());
945
946 // Now create the new database
947 if ((dbret = db_env_create(&conv->dbenv, 0))) {
948 // can't make env handle
949 tqslTrace("open_db", "Can't make db handle: %s", db_strerror(dbret));
950 dbinit_cleanup = true;
951 goto dbinit_end;
952 }
953 if (conv->errfile)
954 conv->dbenv->set_errfile(conv->dbenv, conv->errfile);
955 if (conv->dbenv->set_lg_max)
956 conv->dbenv->set_lg_max(conv->dbenv, 256 * 1024);
957 if (conv->dbenv->set_lk_max_locks)
958 conv->dbenv->set_lk_max_locks(conv->dbenv, 20000);
959 if (conv->dbenv->set_lk_max_objects)
960 conv->dbenv->set_lk_max_objects(conv->dbenv, 20000);
961 if ((dbret = conv->dbenv->open(conv->dbenv, conv->dbpath, envflags, 0600))) {
962 tqslTrace("open_db", "Error opening db: %s", db_strerror(dbret));
963 if (conv->errfile)
964 fprintf(conv->errfile, "opening DB %s returns status %d\n", conv->dbpath, dbret);
965 dbinit_cleanup = true;
966 goto dbinit_end;
967 }
968
969 if ((dbret = db_create(&conv->seendb, conv->dbenv, 0))) {
970 // can't create db
971 tqslTrace("open_db", "Error creating db: %s", db_strerror(dbret));
972 dbinit_cleanup = true;
973 goto dbinit_end;
974 }
975
976 // Create the new database
977 if ((dbret = conv->seendb->open(conv->seendb, NULL, "duplicates.db", NULL, DB_HASH, DB_CREATE, 0600))) {
978 // can't open the db
979 tqslTrace("open_db", "Error opening new db: %s", db_strerror(dbret));
980 dbinit_cleanup = true;
981 goto dbinit_end;
982 }
983 fseek(dmp, 0, SEEK_SET);
984
985 char d[1]= {'D'};
986 memset(&dbkey, 0, sizeof dbkey);
987 memset(&dbdata, 0, sizeof dbdata);
988 dbdata.data = d;
989 dbdata.size = 1;
990
991 while (fgets(duprec, sizeof duprec, dmp)) {
992 dbkey.data = duprec;
993 dbkey.size = strlen(duprec) - 1;
994 conv->seendb->put(conv->seendb, NULL, &dbkey, &dbdata, 0);
995 }
996 conv->seendb->close(conv->seendb, 0);
997 conv->dbenv->close(conv->dbenv, 0);
998 goto reopen;
999 }
1000
1001 dbinit_end:
1002 if (dbinit_cleanup) {
1003 tqslTrace("open_db", "DB open failed, triedDelete=%d", triedDelete);
1004 tQSL_Error = TQSL_DB_ERROR;
1005 tQSL_Errno = errno;
1006 strncpy(tQSL_CustomError, db_strerror(dbret), sizeof tQSL_CustomError);
1007 tqslTrace("open_db", "Error opening db: %s", tQSL_CustomError);
1008 if (conv->txn) conv->txn->abort(conv->txn);
1009 if (conv->seendb) conv->seendb->close(conv->seendb, 0);
1010 conv->db_open = false;
1011 if (conv->dbenv) {
1012 if (conv->dbpath) {
1013 conv->dbenv->remove(conv->dbenv, conv->dbpath, DB_FORCE);
1014 free(conv->dbpath);
1015 conv->dbpath = NULL;
1016 }
1017 conv->dbenv->close(conv->dbenv, 0);
1018 }
1019 if (conv->cursor) conv->cursor->close(conv->cursor);
1020 if (conv->errfile) fclose(conv->errfile);
1021 conv->txn = NULL;
1022 conv->dbenv = NULL;
1023 conv->cursor = NULL;
1024 conv->seendb = NULL;
1025 conv->errfile = NULL;
1026 // Handle case where the database is just broken
1027 if (dbret == EINVAL && !triedDelete) {
1028 tqslTrace("open_db", "EINVAL. Removing db");
1029 remove_db(fixedpath.c_str());
1030 triedDelete = true;
1031 goto reopen;
1032 }
1033 return false;
1034 }
1035 conv->db_open = true;
1036 return true;
1037 }
1038 #endif // USE_LMDB
1039
1040 DLLEXPORT const char* CALLCONVENTION
1041 tqsl_getConverterGABBI(tQSL_Converter convp) {
1042 TQSL_CONVERTER *conv;
1043 char signdata[1024];
1044
1045 if (!(conv = check_conv(convp)))
1046 return 0;
1047 if (conv->need_ident_rec) {
1048 int major = 0, minor = 0, config_major = 0, config_minor = 0;
1049 tqsl_getVersion(&major, &minor);
1050 tqsl_getConfigVersion(&config_major, &config_minor);
1051 char temp[512];
1052 static char ident[512];
1053 snprintf(temp, sizeof temp, "%s Lib: V%d.%d Config: V%d.%d AllowDupes: %s",
1054 conv->appName ? conv->appName : "Unknown",
1055 major, minor, config_major, config_minor,
1056 conv->allow_dupes ? "true" : "false");
1057 temp[sizeof temp - 1] = '\0';
1058 int len = strlen(temp);
1059 snprintf(ident, sizeof ident, "<TQSL_IDENT:%d>%s\n", len, temp);
1060 ident[sizeof ident - 1] = '\0';
1061 conv->need_ident_rec = false;
1062 return ident;
1063 }
1064
1065 if (conv->need_station_rec) {
1066 int uid = conv->cert_idx + conv->base_idx;
1067 conv->need_station_rec = false;
1068 const char *tStation = tqsl_getGABBItSTATION(conv->loc, uid, uid);
1069 tqsl_getCertificateSerialExt(conv->certs[conv->cert_idx], conv->serial, sizeof conv->serial);
1070 tqsl_getCertificateCallSign(conv->certs[conv->cert_idx], conv->callsign, sizeof conv->callsign);
1071 return tStation;
1072 }
1073 if (!conv->allow_dupes && !conv->db_open) {
1074 if (!open_db(conv, false)) { // If can't open dupes DB
1075 return 0;
1076 }
1077 }
1078
1079 TQSL_ADIF_GET_FIELD_ERROR stat;
1080
1081 if (conv->rec_done) {
1082 //cerr << "Getting rec" << endl;
1083 conv->rec_done = false;
1084 conv->clearRec();
1085 int cstat = 0;
1086 int saveErr = 0;
1087 if (conv->adif) {
1088 while (1) {
1089 tqsl_adifFieldResults result;
1090 if (tqsl_getADIFField(conv->adif, &result, &stat, adif_qso_record_fields, notypes, adif_allocate))
1091 break;
1092 if (stat != TQSL_ADIF_GET_FIELD_SUCCESS && stat != TQSL_ADIF_GET_FIELD_NO_NAME_MATCH)
1093 break;
1094 if (!strcasecmp(result.name, "eor"))
1095 break;
1096 if (!strcasecmp(result.name, "CALL") && result.data) {
1097 conv->rec.callsign_set = true;
1098 strncpy(conv->rec.callsign, reinterpret_cast<char *>(result.data), sizeof conv->rec.callsign);
1099 } else if (!strcasecmp(result.name, "BAND") && result.data) {
1100 conv->rec.band_set = true;
1101 strncpy(conv->rec.band, reinterpret_cast<char *>(result.data), sizeof conv->rec.band);
1102 } else if (!strcasecmp(result.name, "MODE") && result.data) {
1103 conv->rec.mode_set = true;
1104 strncpy(conv->rec.mode, reinterpret_cast<char *>(result.data), sizeof conv->rec.mode);
1105 } else if (!strcasecmp(result.name, "SUBMODE") && result.data) {
1106 strncpy(conv->rec.submode, reinterpret_cast<char *>(result.data), sizeof conv->rec.submode);
1107 } else if (!strcasecmp(result.name, "FREQ") && result.data) {
1108 conv->rec.band_set = true;
1109 strncpy(conv->rec.freq, fix_freq(reinterpret_cast<char *>(result.data)), sizeof conv->rec.freq);
1110 if (atof(conv->rec.freq) == 0.0)
1111 conv->rec.freq[0] = '\0';
1112 } else if (!strcasecmp(result.name, "FREQ_RX") && result.data) {
1113 strncpy(conv->rec.rxfreq, fix_freq(reinterpret_cast<char *>(result.data)), sizeof conv->rec.rxfreq);
1114 if (atof(conv->rec.rxfreq) == 0.0)
1115 conv->rec.rxfreq[0] = '\0';
1116 } else if (!strcasecmp(result.name, "BAND_RX") && result.data) {
1117 strncpy(conv->rec.rxband, reinterpret_cast<char *>(result.data), sizeof conv->rec.rxband);
1118 } else if (!strcasecmp(result.name, "SAT_NAME") && result.data) {
1119 strncpy(conv->rec.satname, reinterpret_cast<char *>(result.data), sizeof conv->rec.satname);
1120 } else if (!strcasecmp(result.name, "PROP_MODE") && result.data) {
1121 strncpy(conv->rec.propmode, reinterpret_cast<char *>(result.data), sizeof conv->rec.propmode);
1122 } else if (!strcasecmp(result.name, "QSO_DATE") && result.data) {
1123 conv->rec.date_set = true;
1124 cstat = tqsl_initDate(&(conv->rec.date), (const char *)result.data);
1125 if (cstat)
1126 saveErr = tQSL_Error;
1127 } else if (!strcasecmp(result.name, "TIME_ON") && result.data) {
1128 conv->rec.time_set = true;
1129 cstat = tqsl_initTime(&(conv->rec.time), (const char *)result.data);
1130 if (cstat)
1131 saveErr = tQSL_Error;
1132 }
1133 if (stat == TQSL_ADIF_GET_FIELD_SUCCESS) {
1134 conv->rec_text += string(reinterpret_cast<char *>(result.name)) + ": ";
1135 if (result.data)
1136 conv->rec_text += string(reinterpret_cast<char *>(result.data));
1137 conv->rec_text += "\n";
1138 }
1139 if (result.data)
1140 delete[] result.data;
1141 }
1142 if (saveErr) {
1143 tQSL_Error = saveErr;
1144 conv->rec_done = true;
1145 return 0;
1146 }
1147 if (stat == TQSL_ADIF_GET_FIELD_EOF)
1148 return 0;
1149 if (stat != TQSL_ADIF_GET_FIELD_SUCCESS) {
1150 tQSL_ADIF_Error = stat;
1151 tQSL_Error = TQSL_ADIF_ERROR;
1152 return 0;
1153 }
1154 // ADIF record is complete. See if we need to infer the BAND fields.
1155 if (conv->rec.band[0] == 0)
1156 strncpy(conv->rec.band, tqsl_infer_band(conv->rec.freq), sizeof conv->rec.band);
1157 if (conv->rec.rxband[0] == 0)
1158 strncpy(conv->rec.rxband, tqsl_infer_band(conv->rec.rxfreq), sizeof conv->rec.rxband);
1159 } else if (conv->cab) {
1160 TQSL_CABRILLO_ERROR_TYPE stat;
1161 do {
1162 tqsl_cabrilloField field;
1163 if (tqsl_getCabrilloField(conv->cab, &field, &stat))
1164 return 0;
1165 if (stat == TQSL_CABRILLO_NO_ERROR || stat == TQSL_CABRILLO_EOR) {
1166 // Field found
1167 if (!strcasecmp(field.name, "CALL")) {
1168 conv->rec.callsign_set = true;
1169 strncpy(conv->rec.callsign, field.value, sizeof conv->rec.callsign);
1170 } else if (!strcasecmp(field.name, "BAND")) {
1171 conv->rec.band_set = true;
1172 strncpy(conv->rec.band, field.value, sizeof conv->rec.band);
1173 } else if (!strcasecmp(field.name, "MODE")) {
1174 conv->rec.mode_set = true;
1175 strncpy(conv->rec.mode, field.value, sizeof conv->rec.mode);
1176 } else if (!strcasecmp(field.name, "FREQ")) {
1177 conv->rec.band_set = true;
1178 strncpy(conv->rec.freq, field.value, sizeof conv->rec.freq);
1179 } else if (!strcasecmp(field.name, "QSO_DATE")) {
1180 conv->rec.date_set = true;
1181 cstat = tqsl_initDate(&(conv->rec.date), field.value);
1182 if (cstat)
1183 saveErr = tQSL_Error;
1184 } else if (!strcasecmp(field.name, "TIME_ON")) {
1185 conv->rec.time_set = true;
1186 cstat = tqsl_initTime(&(conv->rec.time), field.value);
1187 if (cstat)
1188 saveErr = tQSL_Error;
1189 }
1190 if (conv->rec_text != "")
1191 conv->rec_text += "\n";
1192 conv->rec_text += string(field.name) + ": " + field.value;
1193 }
1194 } while (stat == TQSL_CABRILLO_NO_ERROR);
1195 if (saveErr)
1196 tQSL_Error = saveErr;
1197 if (saveErr || stat != TQSL_CABRILLO_EOR) {
1198 conv->rec_done = true;
1199 return 0;
1200 }
1201 } else {
1202 tQSL_Error = TQSL_CUSTOM_ERROR;
1203 strncpy(tQSL_CustomError, "Converter not initialized", sizeof tQSL_CustomError);
1204 tqslTrace("tqsl_getConverterGABBI", "Converter not initialized");
1205 return 0;
1206 }
1207 }
1208 // Does the QSO have the basic required elements?
1209 if (!conv->rec.callsign_set) {
1210 conv->rec_done = true;
1211 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a Callsign");
1212 tQSL_Error = TQSL_CUSTOM_ERROR;
1213 return 0;
1214 }
1215 if (!conv->rec.band_set) {
1216 conv->rec_done = true;
1217 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a band or frequency");
1218 tQSL_Error = TQSL_CUSTOM_ERROR;
1219 return 0;
1220 }
1221 if (!conv->rec.mode_set) {
1222 conv->rec_done = true;
1223 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a mode");
1224 tQSL_Error = TQSL_CUSTOM_ERROR;
1225 return 0;
1226 }
1227 if (!conv->rec.date_set) {
1228 conv->rec_done = true;
1229 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a date");
1230 tQSL_Error = TQSL_CUSTOM_ERROR;
1231 return 0;
1232 }
1233 if (!conv->rec.time_set) {
1234 conv->rec_done = true;
1235 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid contact - QSO does not specify a time");
1236 tQSL_Error = TQSL_CUSTOM_ERROR;
1237 return 0;
1238 }
1239
1240 // Check QSO date against user-specified date range.
1241 if (tqsl_isDateValid(&(conv->rec.date))) {
1242 if (tqsl_isDateValid(&(conv->start)) && tqsl_compareDates(&(conv->rec.date), &(conv->start)) < 0) {
1243 conv->rec_done = true;
1244 tQSL_Error = TQSL_DATE_OUT_OF_RANGE;
1245 return 0;
1246 }
1247 if (tqsl_isDateValid(&(conv->end)) && tqsl_compareDates(&(conv->rec.date), &(conv->end)) > 0) {
1248 conv->rec_done = true;
1249 tQSL_Error = TQSL_DATE_OUT_OF_RANGE;
1250 return 0;
1251 }
1252 }
1253
1254 // Do field value mapping
1255 tqsl_strtoupper(conv->rec.callsign);
1256 if (!conv->allow_bad_calls) {
1257 if (!checkCallSign(conv->rec.callsign)) {
1258 conv->rec_done = true;
1259 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid amateur CALL (%s)", conv->rec.callsign);
1260 tQSL_Error = TQSL_CUSTOM_ERROR;
1261 return 0;
1262 }
1263 }
1264 tqsl_strtoupper(conv->rec.band);
1265 tqsl_strtoupper(conv->rec.rxband);
1266 tqsl_strtoupper(conv->rec.mode);
1267 tqsl_strtoupper(conv->rec.submode);
1268 char val[256] = "";
1269 // Try to find the GABBI mode several ways.
1270 val[0] = '\0';
1271 if (conv->rec.submode[0] != '\0') {
1272 char modeSub[256];
1273 strncpy(modeSub, conv->rec.mode, sizeof modeSub);
1274 size_t left = sizeof modeSub - strlen(modeSub);
1275 strncat(modeSub, "%", left);
1276 left = sizeof modeSub - strlen(modeSub);
1277 strncat(modeSub, conv->rec.submode, left);
1278 if (tqsl_getADIFMode(modeSub, val, sizeof val)) { // mode%submode lookup failed
1279 // Try just the submode.
1280 if (tqsl_getADIFMode(conv->rec.submode, val, sizeof val)) { // bare submode failed
1281 if (tqsl_getADIFMode(conv->rec.mode, val, sizeof val)) {
1282 val[0] = '\0';
1283 }
1284 }
1285 }
1286 } else {
1287 // Just a mode, no submode. Look that up.
1288 tqsl_getADIFMode(conv->rec.mode, val, sizeof val);
1289 }
1290 if (val[0] != '\0')
1291 strncpy(conv->rec.mode, val, sizeof conv->rec.mode);
1292 // Check field validities
1293 if (conv->modes.find(conv->rec.mode) == conv->modes.end()) {
1294 conv->rec_done = true;
1295 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid MODE (%s)", conv->rec.mode);
1296 tQSL_Error = TQSL_CUSTOM_ERROR;
1297 return 0;
1298 }
1299 if (conv->bands.find(conv->rec.band) == conv->bands.end()) {
1300 conv->rec_done = true;
1301 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid BAND (%s)", conv->rec.band);
1302 tQSL_Error = TQSL_CUSTOM_ERROR;
1303 return 0;
1304 }
1305 if (conv->rec.rxband[0] && (conv->bands.find(conv->rec.rxband) == conv->bands.end())) {
1306 conv->rec_done = true;
1307 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid RX BAND (%s)", conv->rec.rxband);
1308 tQSL_Error = TQSL_CUSTOM_ERROR;
1309 return 0;
1310 }
1311 if (conv->rec.freq[0] && strcmp(conv->rec.band, tqsl_infer_band(conv->rec.freq))) {
1312 conv->rec_done = true;
1313 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Frequency %s is out of range for band %s", conv->rec.freq, conv->rec.band);
1314 tQSL_Error = TQSL_CUSTOM_ERROR;
1315 return 0;
1316 }
1317 if (conv->rec.rxfreq[0] && strcmp(conv->rec.rxband, tqsl_infer_band(conv->rec.rxfreq))) {
1318 conv->rec_done = true;
1319 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "RX Frequency %s is out of range for band %s", conv->rec.rxfreq, conv->rec.rxband);
1320 tQSL_Error = TQSL_CUSTOM_ERROR;
1321 return 0;
1322 }
1323 if (conv->rec.propmode[0] != '\0'
1324 && conv->propmodes.find(conv->rec.propmode) == conv->propmodes.end()) {
1325 conv->rec_done = true;
1326 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid PROP_MODE (%s)", conv->rec.propmode);
1327 tQSL_Error = TQSL_CUSTOM_ERROR;
1328 return 0;
1329 }
1330 if (conv->rec.satname[0] != '\0'
1331 && conv->satellites.find(conv->rec.satname) == conv->satellites.end()) {
1332 conv->rec_done = true;
1333 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "Invalid SAT_NAME (%s)", conv->rec.satname);
1334 tQSL_Error = TQSL_CUSTOM_ERROR;
1335 return 0;
1336 }
1337 if (!strcmp(conv->rec.propmode, "SAT") && conv->rec.satname[0] == '\0') {
1338 conv->rec_done = true;
1339 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "PROP_MODE = 'SAT' but no SAT_NAME");
1340 tQSL_Error = TQSL_CUSTOM_ERROR;
1341 return 0;
1342 }
1343 if (strcmp(conv->rec.propmode, "SAT") && conv->rec.satname[0] != '\0') {
1344 conv->rec_done = true;
1345 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "SAT_NAME set but PROP_MODE is not 'SAT'");
1346 tQSL_Error = TQSL_CUSTOM_ERROR;
1347 return 0;
1348 }
1349
1350 // Check cert
1351 if (conv->ncerts <= 0) {
1352 conv->rec_done = true;
1353 tQSL_Error = TQSL_CERT_NOT_FOUND;
1354 return 0;
1355 }
1356
1357 int cidx = find_matching_cert(conv);
1358 if (cidx < 0) {
1359 conv->rec_done = true;
1360 tQSL_Error = TQSL_CERT_DATE_MISMATCH;
1361 return 0;
1362 }
1363 if (cidx != conv->cert_idx) {
1364 // Switching certs
1365 conv->cert_idx = cidx;
1366 if (!conv->certs_used[conv->cert_idx]) {
1367 // Need to output tCERT, tSTATION
1368 conv->need_station_rec = true;
1369 conv->certs_used[conv->cert_idx] = true;
1370 return tqsl_getGABBItCERT(conv->certs[conv->cert_idx], conv->cert_idx + conv->base_idx);
1371 }
1372 }
1373 const char *grec = tqsl_getGABBItCONTACTData(conv->certs[conv->cert_idx], conv->loc, &(conv->rec),
1374 conv->cert_idx + conv->base_idx, signdata, sizeof(signdata));
1375 if (grec) {
1376 conv->rec_done = true;
1377 if (!conv->allow_dupes) {
1378 char stnloc[128];
1379 char qso[128];
1380 if (tqsl_getLocationStationDetails(conv->loc, stnloc, sizeof stnloc)) {
1381 stnloc[0] = '\0';
1382 }
1383 if (tqsl_getLocationQSODetails(conv->loc, qso, sizeof qso)) {
1384 qso[0] = '\0';
1385 }
1386 // Old-style Lookup uses signdata and cert serial number
1387 #ifdef USE_LMDB
1388 MDB_val dbkey, dbdata;
1389 #else
1390 DBT dbkey, dbdata;
1391 memset(&dbkey, 0, sizeof dbkey);
1392 memset(&dbdata, 0, sizeof dbdata);
1393 #endif
1394 // append signing key serial
1395 strncat(signdata, conv->serial, sizeof(signdata) - strlen(signdata)-1);
1396 // Updated dupe database entry. Key is formed from
1397 // local callsign concatenated with the QSO details
1398 char dupekey[128];
1399 snprintf(dupekey, sizeof dupekey, "%s%s", conv->callsign, qso);
1400 #ifdef USE_LMDB
1401 dbkey.mv_size = strlen(signdata);
1402 dbkey.mv_data = signdata;
1403 int dbget_err = mdb_get(conv->txn, conv->seendb, &dbkey, &dbdata);
1404 #else
1405 dbkey.size = strlen(signdata);
1406 dbkey.data = signdata;
1407 int dbget_err = conv->seendb->get(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1408 #endif
1409 if (0 == dbget_err) {
1410 //lookup was successful; thus this is a duplicate.
1411 tQSL_Error = TQSL_DUPLICATE_QSO;
1412 tQSL_CustomError[0] = '\0';
1413 // delete the old record
1414
1415 int dbput_err;
1416 #ifdef USE_LMDB
1417 mdb_del(conv->txn, conv->seendb, &dbkey, &dbdata);
1418 // Update this to the current format
1419 dbkey.mv_size = strlen(dupekey);
1420 dbkey.mv_data = dupekey;
1421 dbdata.mv_data = stnloc;
1422 dbdata.mv_size = strlen(stnloc);
1423 dbput_err = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1424 #else
1425 conv->seendb->del(conv->seendb, conv->txn, &dbkey, 0);
1426 // Update this to the current format
1427 memset(&dbkey, 0, sizeof dbkey);
1428 dbkey.size = strlen(dupekey);
1429 dbkey.data = dupekey;
1430 memset(&dbdata, 0, sizeof dbdata);
1431 dbdata.data = stnloc;
1432 dbdata.size = strlen(stnloc);
1433 dbput_err = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1434 #endif
1435 if (0 != dbput_err) {
1436 strncpy(tQSL_CustomError, db_strerror(dbput_err), sizeof tQSL_CustomError);
1437 tQSL_Error = TQSL_DB_ERROR;
1438 return 0;
1439 }
1440 return 0;
1441 #ifdef USE_LMDB
1442 } else if (dbget_err != MDB_NOTFOUND) {
1443 #else
1444 } else if (dbget_err != DB_NOTFOUND) {
1445 #endif
1446 //non-zero return, but not "not found" - thus error
1447 strncpy(tQSL_CustomError, db_strerror(dbget_err), sizeof tQSL_CustomError);
1448 tQSL_Error = TQSL_DB_ERROR;
1449 return 0;
1450 // could be more specific but there's very little the user can do at this point anyway
1451 }
1452 #ifdef USE_LMDB
1453 dbkey.mv_size = strlen(dupekey);
1454 dbkey.mv_data = dupekey;
1455 dbget_err = mdb_get(conv->txn, conv->seendb, &dbkey, &dbdata);
1456 #else
1457 memset(&dbkey, 0, sizeof dbkey);
1458 memset(&dbdata, 0, sizeof dbdata);
1459
1460 dbkey.size = strlen(dupekey);
1461 dbkey.data = dupekey;
1462 dbget_err = conv->seendb->get(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1463 #endif
1464 if (0 == dbget_err) {
1465 //lookup was successful; thus this is a duplicate.
1466 tQSL_Error = TQSL_DUPLICATE_QSO;
1467 // Save the original and new station location details so those can be provided
1468 // with an error by the caller
1469 #ifdef USE_LMDB
1470 char *olddup = reinterpret_cast<char *> (malloc(dbdata.mv_size + 2));
1471 memcpy(olddup, dbdata.mv_data, dbdata.mv_size);
1472 olddup[dbdata.mv_size] = '\0';
1473 #else
1474 char *olddup = reinterpret_cast<char *> (malloc(dbdata.size + 2));
1475 memcpy(olddup, dbdata.data, dbdata.size);
1476 olddup[dbdata.size] = '\0';
1477 #endif
1478 snprintf(tQSL_CustomError, sizeof tQSL_CustomError, "%s|%s", olddup, stnloc);
1479 free(olddup);
1480 return 0;
1481 #ifdef USE_LMDB
1482 } else if (dbget_err != MDB_NOTFOUND) {
1483 #else
1484 } else if (dbget_err != DB_NOTFOUND) {
1485 #endif
1486 //non-zero return, but not "not found" - thus error
1487 strncpy(tQSL_CustomError, db_strerror(dbget_err), sizeof tQSL_CustomError);
1488 tQSL_Error = TQSL_DB_ERROR;
1489 return 0;
1490 // could be more specific but there's very little the user can do at this point anyway
1491 }
1492
1493 int dbput_err;
1494 #ifdef USE_LMDB
1495 dbdata.mv_data = stnloc;
1496 dbdata.mv_size = strlen(stnloc);
1497 dbput_err = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1498 #else
1499 memset(&dbdata, 0, sizeof dbdata);
1500 dbdata.data = stnloc;
1501 dbdata.size = strlen(stnloc);
1502 dbput_err = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1503 #endif
1504 if (0 != dbput_err) {
1505 strncpy(tQSL_CustomError, db_strerror(dbput_err), sizeof tQSL_CustomError);
1506 tQSL_Error = TQSL_DB_ERROR;
1507 return 0;
1508 }
1509 }
1510 }
1511 return grec;
1512 }
1513
1514 DLLEXPORT int CALLCONVENTION
1515 tqsl_getConverterCert(tQSL_Converter convp, tQSL_Cert *certp) {
1516 TQSL_CONVERTER *conv;
1517 if (!(conv = check_conv(convp)))
1518 return 1;
1519 if (certp == 0) {
1520 tQSL_Error = TQSL_ARGUMENT_ERROR;
1521 return 1;
1522 }
1523 *certp = conv->certs[conv->cert_idx];
1524 return 0;
1525 }
1526
1527 DLLEXPORT int CALLCONVENTION
1528 tqsl_getConverterLine(tQSL_Converter convp, int *lineno) {
1529 TQSL_CONVERTER *conv;
1530 if (!(conv = check_conv(convp)))
1531 return 1;
1532 if (lineno == 0) {
1533 tQSL_Error = TQSL_ARGUMENT_ERROR;
1534 return 1;
1535 }
1536 if (conv->cab)
1537 return tqsl_getCabrilloLine(conv->cab, lineno);
1538 else if (conv->adif)
1539 return tqsl_getADIFLine(conv->adif, lineno);
1540 *lineno = 0;
1541 return 0;
1542 }
1543
1544 DLLEXPORT const char* CALLCONVENTION
1545 tqsl_getConverterRecordText(tQSL_Converter convp) {
1546 TQSL_CONVERTER *conv;
1547 if (!(conv = check_conv(convp)))
1548 return 0;
1549 return conv->rec_text.c_str();
1550 }
1551
1552 DLLEXPORT int CALLCONVENTION
1553 tqsl_setConverterAllowBadCall(tQSL_Converter convp, int allow) {
1554 TQSL_CONVERTER *conv;
1555 if (!(conv = check_conv(convp)))
1556 return 1;
1557 conv->allow_bad_calls = (allow != 0);
1558 return 0;
1559 }
1560
1561 DLLEXPORT int CALLCONVENTION
1562 tqsl_setConverterAllowDuplicates(tQSL_Converter convp, int allow) {
1563 TQSL_CONVERTER *conv;
1564 if (!(conv = check_conv(convp)))
1565 return 1;
1566 conv->allow_dupes = (allow != 0);
1567 return 0;
1568 }
1569
1570 DLLEXPORT int CALLCONVENTION
1571 tqsl_setConverterAppName(tQSL_Converter convp, const char *app) {
1572 TQSL_CONVERTER *conv;
1573 if (!(conv = check_conv(convp)))
1574 return 1;
1575 if (!app) {
1576 tQSL_Error = TQSL_ARGUMENT_ERROR;
1577 return 1;
1578 }
1579 conv->appName = strdup(app);
1580 return 0;
1581 }
1582
1583 DLLEXPORT int CALLCONVENTION
1584 tqsl_converterRollBack(tQSL_Converter convp) {
1585 TQSL_CONVERTER *conv;
1586
1587 tqslTrace("tqsl_converterRollBack", NULL);
1588 if (!(conv = check_conv(convp)))
1589 return 1;
1590 if (!conv->db_open)
1591 return 0;
1592 if (conv->txn)
1593 #ifdef USE_LMDB
1594 mdb_txn_abort(conv->txn);
1595 #else
1596 conv->txn->abort(conv->txn);
1597 #endif
1598 conv->txn = NULL;
1599 return 0;
1600 }
1601
1602 DLLEXPORT int CALLCONVENTION
1603 tqsl_converterCommit(tQSL_Converter convp) {
1604 TQSL_CONVERTER *conv;
1605
1606 tqslTrace("tqsl_converterCommit", NULL);
1607 if (!(conv = check_conv(convp)))
1608 return 1;
1609 if (!conv->db_open)
1610 return 0;
1611 if (conv->txn)
1612 #ifdef USE_LMDB
1613 mdb_txn_commit(conv->txn);
1614 #else
1615 conv->txn->commit(conv->txn, 0);
1616 #endif
1617 conv->txn = NULL;
1618 return 0;
1619 }
1620
1621 DLLEXPORT int CALLCONVENTION
1622 tqsl_getDuplicateRecords(tQSL_Converter convp, char *key, char *data, int keylen) {
1623 TQSL_CONVERTER *conv;
1624
1625 if (!(conv = check_conv(convp)))
1626 return 1;
1627
1628 if (!conv->db_open) {
1629 if (!open_db(conv, true)) { // If can't open dupes DB
1630 return 1;
1631 }
1632 }
1633 if (!conv->cursor) {
1634 #ifdef USE_LMDB
1635 int err = mdb_cursor_open(conv->txn, conv->seendb, &conv->cursor);
1636 #else
1637 int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
1638 #endif
1639 if (err) {
1640 strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
1641 tQSL_Error = TQSL_DB_ERROR;
1642 tQSL_Errno = errno;
1643 return 1;
1644 }
1645 }
1646
1647 #ifdef USE_LMDB
1648 MDB_val dbkey, dbdata;
1649 int status = mdb_cursor_get(conv->cursor, &dbkey, &dbdata, MDB_NEXT);
1650 if (MDB_NOTFOUND == status) {
1651 #else
1652 DBT dbkey, dbdata;
1653 memset(&dbkey, 0, sizeof dbkey);
1654 memset(&dbdata, 0, sizeof dbdata);
1655 int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
1656 if (DB_NOTFOUND == status) {
1657 #endif
1658 return -1; // No more records
1659 }
1660 if (status != 0) {
1661 strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1662 tQSL_Error = TQSL_DB_ERROR;
1663 tQSL_Errno = errno;
1664 return 1;
1665 }
1666 #ifdef USE_LMDB
1667 memcpy(key, dbkey.mv_data, dbkey.mv_size);
1668 key[dbkey.mv_size] = '\0';
1669
1670 if (dbdata.mv_size > 9) dbdata.mv_size = 9;
1671 memcpy(data, dbdata.mv_data, dbdata.mv_size);
1672 data[dbdata.mv_size] = '\0';
1673 #else
1674 memcpy(key, dbkey.data, dbkey.size);
1675 key[dbkey.size] = '\0';
1676
1677 if (dbdata.size > 9) dbdata.size = 9;
1678 memcpy(data, dbdata.data, dbdata.size);
1679 data[dbdata.size] = '\0';
1680 #endif
1681 return 0;
1682 }
1683
1684 DLLEXPORT int CALLCONVENTION
1685 tqsl_getDuplicateRecordsV2(tQSL_Converter convp, char *key, char *data, int keylen) {
1686 TQSL_CONVERTER *conv;
1687
1688 if (!(conv = check_conv(convp)))
1689 return 1;
1690
1691 if (!conv->db_open) {
1692 if (!open_db(conv, true)) { // If can't open dupes DB
1693 return 1;
1694 }
1695 }
1696 if (!conv->cursor) {
1697 #ifdef USE_LMDB
1698 int err = mdb_cursor_open(conv->txn, conv->seendb, &conv->cursor);
1699 #else
1700 int err = conv->seendb->cursor(conv->seendb, conv->txn, &conv->cursor, DB_CURSOR_BULK);
1701 #endif
1702 if (err) {
1703 strncpy(tQSL_CustomError, db_strerror(err), sizeof tQSL_CustomError);
1704 tQSL_Error = TQSL_DB_ERROR;
1705 tQSL_Errno = errno;
1706 return 1;
1707 }
1708 }
1709
1710 #ifdef USE_LMDB
1711 MDB_val dbkey, dbdata;
1712 int status = mdb_cursor_get(conv->cursor, &dbkey, &dbdata, MDB_NEXT);
1713 if (MDB_NOTFOUND == status) {
1714 #else
1715 DBT dbkey, dbdata;
1716 memset(&dbkey, 0, sizeof dbkey);
1717 memset(&dbdata, 0, sizeof dbdata);
1718 int status = conv->cursor->c_get(conv->cursor, &dbkey, &dbdata, DB_NEXT);
1719 if (DB_NOTFOUND == status) {
1720 #endif
1721 return -1; // No more records
1722 }
1723 if (status != 0) {
1724 strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1725 tQSL_Error = TQSL_DB_ERROR;
1726 tQSL_Errno = errno;
1727 return 1;
1728 }
1729 #ifdef USE_LMDB
1730 memcpy(key, dbkey.mv_data, dbkey.mv_size);
1731 key[dbkey.mv_size] = '\0';
1732 if (dbdata.mv_size > 255) dbdata.mv_size = 255;
1733 memcpy(data, dbdata.mv_data, dbdata.mv_size);
1734 data[dbdata.mv_size] = '\0';
1735 #else
1736 memcpy(key, dbkey.data, dbkey.size);
1737 key[dbkey.size] = '\0';
1738 if (dbdata.size > 255) dbdata.size = 255;
1739 memcpy(data, dbdata.data, dbdata.size);
1740 data[dbdata.size] = '\0';
1741 #endif
1742 return 0;
1743 }
1744
1745 DLLEXPORT int CALLCONVENTION
1746 tqsl_putDuplicateRecord(tQSL_Converter convp, const char *key, const char *data, int keylen) {
1747 TQSL_CONVERTER *conv;
1748
1749 if (!(conv = check_conv(convp)))
1750 return 0;
1751
1752 if (!conv->db_open) {
1753 if (!open_db(conv, false)) { // If can't open dupes DB
1754 return 0;
1755 }
1756 }
1757 #ifdef USE_LMDB
1758 MDB_val dbkey, dbdata;
1759 dbkey.mv_size = keylen;
1760 dbkey.mv_data = const_cast<char *>(key);
1761
1762 dbdata.mv_size = strlen(data);
1763 dbdata.mv_data = const_cast<char *>(data);
1764
1765 int status = mdb_put(conv->txn, conv->seendb, &dbkey, &dbdata, 0);
1766
1767 if (MDB_KEYEXIST == status) {
1768 return -1; // OK, but already there
1769 }
1770
1771 #else
1772 DBT dbkey, dbdata;
1773 memset(&dbkey, 0, sizeof dbkey);
1774 memset(&dbdata, 0, sizeof dbdata);
1775 dbkey.size = keylen;
1776 dbkey.data = const_cast<char *>(key);
1777
1778 dbdata.size = strlen(data);
1779 dbdata.data = const_cast<char *>(data);
1780
1781 int status = conv->seendb->put(conv->seendb, conv->txn, &dbkey, &dbdata, 0);
1782
1783 if (DB_KEYEXIST == status) {
1784 return -1; // OK, but already there
1785 }
1786 #endif
1787
1788 if (status != 0) {
1789 strncpy(tQSL_CustomError, db_strerror(status), sizeof tQSL_CustomError);
1790 tQSL_Error = TQSL_DB_ERROR;
1791 tQSL_Errno = errno;
1792 return 1;
1793 }
1794 return 0;
1795 }
1796
1797 static bool
1798 hasValidCallSignChars(const string& call) {
1799 // Check for invalid characters
1800 if (call.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/") != string::npos)
1801 return false;
1802 // Need at least one letter
1803 if (call.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ") == string::npos)
1804 return false;
1805 // Need at least one number
1806 if (call.find_first_of("0123456789") == string::npos)
1807 return false;
1808 // Invalid callsign patterns
1809 // Starting with 0, Q
1810 // 1x other than 1A, 1M, 1S
1811 string first = call.substr(0, 1);
1812 string second = call.substr(1, 1);
1813 if (first == "0" || first == "Q" ||
1814 #ifdef MARK_C7_4Y_INVALID
1815 (first == "C" && second == "7") ||
1816 (first == "4" && second == "Y") ||
1817 #endif
1818 (first == "1" && second != "A" && second != "M" && second != "S"))
1819 return false;
1820
1821 return true;
1822 }
1823
1824 static bool
1825 checkCallSign(const string& call) {
1826 if (!hasValidCallSignChars(call))
1827 return false;
1828 if (call.length() < 3)
1829 return false;
1830 string::size_type idx, newidx;
1831 for (idx = 0; idx != string::npos; idx = newidx+1) {
1832 string s;
1833 newidx = call.find('/', idx);
1834 if (newidx == string::npos)
1835 s = call.substr(idx);
1836 else
1837 s = call.substr(idx, newidx - idx);
1838 if (s.length() == 0)
1839 return false; // Leading or trailing '/' is bad, bad!
1840 if (newidx == string::npos)
1841 break;
1842 }
1843 return true;
1844 }
1845