1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <platform.h>
26 #include <file_lib.h>
27
28 #include <mutex.h> /* ThreadLock */
29 #include <dbm_api.h>
30 #include <dbm_priv.h>
31 #include <dbm_migration.h>
32 #include <cleanup.h>
33 #include <logging.h>
34 #include <misc_lib.h>
35 #include <known_dirs.h>
36 #include <string_lib.h>
37 #include <time.h> /* time() */
38
39
40 static bool DBPathLock(FileLock *lock, const char *filename);
41 static void DBPathUnLock(FileLock *lock);
42 static void DBPathMoveBroken(const char *filename);
43
44 struct DBHandle_
45 {
46 /* Filename of database file */
47 char *filename;
48
49 /* Name of specific sub-db */
50 char *subname;
51
52 /* Actual database-specific data */
53 DBPriv *priv;
54
55 int refcount;
56
57 /* This lock protects initialization of .priv element, and .refcount manipulation */
58 pthread_mutex_t lock;
59
60 /* Record when the DB was opened (to check if possible corruptions are
61 * already repaired) */
62 time_t open_tstamp;
63
64 /**
65 * @see FreezeDB()
66 */
67 bool frozen;
68 };
69
70 struct DBCursor_
71 {
72 DBCursorPriv *cursor;
73 };
74
75 typedef struct dynamic_db_handles_
76 {
77 DBHandle *handle;
78 struct dynamic_db_handles_ *next;
79 } DynamicDBHandles;
80
81 /******************************************************************************/
82
83 /*
84 * This lock protects on-demand initialization of db_handles[i].lock and
85 * db_handles[i].name.
86 */
87 static pthread_mutex_t db_handles_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; /* GLOBAL_T */
88
89 static DBHandle db_handles[dbid_max] = { { 0 } }; /* GLOBAL_X */
90 static DynamicDBHandles *db_dynamic_handles;
91
92 static pthread_once_t db_shutdown_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */
93
94 /******************************************************************************/
95
96 // Only append to the end, keep in sync with dbid enum in dbm_api.h
97 static const char *const DB_PATHS_STATEDIR[] = {
98 [dbid_classes] = "cf_classes",
99 [dbid_variables] = "cf_variables",
100 [dbid_performance] = "performance",
101 [dbid_checksums] = "checksum_digests",
102 [dbid_filestats] = "stats",
103 [dbid_changes] = "cf_changes",
104 [dbid_observations] = "cf_observations",
105 [dbid_state] = "cf_state",
106 [dbid_lastseen] = "cf_lastseen",
107 [dbid_audit] = "cf_audit",
108 [dbid_locks] = "cf_lock",
109 [dbid_history] = "history",
110 [dbid_measure] = "nova_measures",
111 [dbid_static] = "nova_static",
112 [dbid_scalars] = "nova_pscalar",
113 [dbid_windows_registry] = "mswin",
114 [dbid_cache] = "nova_cache",
115 [dbid_license] = "nova_track",
116 [dbid_value] = "nova_value",
117 [dbid_agent_execution] = "nova_agent_execution",
118 [dbid_bundles] = "bundles",
119 [dbid_packages_installed] = "packages_installed",
120 [dbid_packages_updates] = "packages_updates",
121 [dbid_cookies] = "nova_cookies",
122 };
123
124 /*
125 These are the old (pre 3.7) paths in workdir, supported for installations that
126 still have them. We will never create a database here. NULL means that the
127 database was always in the state directory.
128 */
129 static const char *const DB_PATHS_WORKDIR[sizeof(DB_PATHS_STATEDIR) / sizeof(const char * const)] = {
130 [dbid_classes] = "cf_classes",
131 [dbid_variables] = NULL,
132 [dbid_performance] = "performance",
133 [dbid_checksums] = "checksum_digests",
134 [dbid_filestats] = "stats",
135 [dbid_changes] = NULL,
136 [dbid_observations] = NULL,
137 [dbid_state] = NULL,
138 [dbid_lastseen] = "cf_lastseen",
139 [dbid_audit] = "cf_audit",
140 [dbid_locks] = NULL,
141 [dbid_history] = NULL,
142 [dbid_measure] = NULL,
143 [dbid_static] = NULL,
144 [dbid_scalars] = NULL,
145 [dbid_windows_registry] = "mswin",
146 [dbid_cache] = "nova_cache",
147 [dbid_license] = "nova_track",
148 [dbid_value] = "nova_value",
149 [dbid_agent_execution] = "nova_agent_execution",
150 [dbid_bundles] = "bundles",
151 };
152
153 /******************************************************************************/
154
DBIdToSubPath(dbid id,const char * subdb_name)155 char *DBIdToSubPath(dbid id, const char *subdb_name)
156 {
157 char *filename;
158 if (xasprintf(&filename, "%s/%s_%s.%s", GetStateDir(), DB_PATHS_STATEDIR[id],
159 subdb_name, DBPrivGetFileExtension()) == -1)
160 {
161 ProgrammingError("Unable to construct sub database filename for file"
162 "%s_%s", DB_PATHS_STATEDIR[id], subdb_name);
163 }
164
165 char *native_filename = MapNameCopy(filename);
166 free(filename);
167
168 return native_filename;
169 }
170
DBIdToPath(dbid id)171 char *DBIdToPath(dbid id)
172 {
173 assert(DB_PATHS_STATEDIR[id] != NULL);
174
175 char *filename = NULL;
176
177 if (DB_PATHS_WORKDIR[id])
178 {
179 xasprintf(&filename, "%s/%s.%s", GetWorkDir(), DB_PATHS_WORKDIR[id],
180 DBPrivGetFileExtension());
181 struct stat statbuf;
182 if (stat(filename, &statbuf) == -1)
183 {
184 // Old database in workdir is not there. Use new database in statedir.
185 free(filename);
186 filename = NULL;
187 }
188 }
189
190 if (!filename)
191 {
192 xasprintf(&filename, "%s/%s.%s", GetStateDir(), DB_PATHS_STATEDIR[id],
193 DBPrivGetFileExtension());
194 }
195
196 char *native_filename = MapNameCopy(filename);
197 free(filename);
198
199 return native_filename;
200 }
201
202 static
IsSubHandle(DBHandle * handle,dbid id,const char * name)203 bool IsSubHandle(DBHandle *handle, dbid id, const char *name)
204 {
205 char *sub_path = DBIdToSubPath(id, name);
206 bool result = StringEqual(handle->filename, sub_path);
207 free(sub_path);
208 return result;
209 }
210
DBHandleGetSubDB(dbid id,const char * name)211 static DBHandle *DBHandleGetSubDB(dbid id, const char *name)
212 {
213 ThreadLock(&db_handles_lock);
214
215 DynamicDBHandles *handles_list = db_dynamic_handles;
216
217 while (handles_list)
218 {
219 if (IsSubHandle(handles_list->handle, id, name))
220 {
221 ThreadUnlock(&db_handles_lock);
222 return handles_list->handle;
223 }
224 handles_list = handles_list->next;
225 }
226
227 DBHandle *handle = xcalloc(1, sizeof(DBHandle));
228 handle->filename = DBIdToSubPath(id, name);
229 handle->subname = SafeStringDuplicate(name);
230
231 /* Initialize mutexes as error-checking ones. */
232 pthread_mutexattr_t attr;
233 pthread_mutexattr_init(&attr);
234 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
235 pthread_mutex_init(&handle->lock, &attr);
236 pthread_mutexattr_destroy(&attr);
237
238 /* Prepend handle to global list. */
239 handles_list = xcalloc(1, sizeof(DynamicDBHandles));
240 handles_list->handle = handle;
241 handles_list->next = db_dynamic_handles;
242 db_dynamic_handles = handles_list;
243
244 ThreadUnlock(&db_handles_lock);
245
246 return handle;
247 }
248
DBHandleGet(int id)249 static DBHandle *DBHandleGet(int id)
250 {
251 assert(id >= 0 && id < dbid_max);
252
253 ThreadLock(&db_handles_lock);
254 if (db_handles[id].filename == NULL)
255 {
256 db_handles[id].filename = DBIdToPath(id);
257
258 /* Initialize mutexes as error-checking ones. */
259 pthread_mutexattr_t attr;
260 pthread_mutexattr_init(&attr);
261 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
262 pthread_mutex_init(&db_handles[id].lock, &attr);
263 pthread_mutexattr_destroy(&attr);
264 }
265
266 ThreadUnlock(&db_handles_lock);
267
268 return &db_handles[id];
269 }
270
271 static inline
CloseDBInstance(DBHandle * handle)272 void CloseDBInstance(DBHandle *handle)
273 {
274 /* Wait until all DB users are served, or a threshold is reached */
275 int count = 0;
276 ThreadLock(&handle->lock);
277 if (handle->frozen)
278 {
279 /* Just clean some allocated memory, but don't touch the DB itself. */
280 free(handle->filename);
281 free(handle->subname);
282 ThreadUnlock(&handle->lock);
283 return;
284 }
285 while (handle->refcount > 0 && count < 1000)
286 {
287 ThreadUnlock(&handle->lock);
288
289 struct timespec sleeptime = {
290 .tv_sec = 0,
291 .tv_nsec = 10000000 /* 10 ms */
292 };
293 nanosleep(&sleeptime, NULL);
294 count++;
295
296 ThreadLock(&handle->lock);
297 }
298 /* Keep mutex locked. */
299
300 /* If we exited because of timeout make sure we Log() it. */
301 if (handle->refcount != 0)
302 {
303 Log(LOG_LEVEL_ERR,
304 "Database %s refcount is still not zero (%d), forcing CloseDB()!",
305 handle->filename, handle->refcount);
306 DBPrivCloseDB(handle->priv);
307 }
308 else /* TODO: can we clean this up unconditionally ? */
309 {
310 free(handle->filename);
311 free(handle->subname);
312 handle->filename = NULL;
313 }
314 }
315
316
317 /**
318 * @brief Wait for all users of all databases to close the DBs. Then acquire
319 * the mutexes *AND KEEP THEM LOCKED* so that no background thread can open
320 * any database. So make sure you exit soon...
321 *
322 * @warning This is usually register with atexit(), however you have to make
323 * sure no other DB-cleaning exit hook was registered before, so that this is
324 * called last.
325 **/
CloseAllDBExit()326 void CloseAllDBExit()
327 {
328 ThreadLock(&db_handles_lock);
329
330 for (int i = 0; i < dbid_max; i++)
331 {
332 if (db_handles[i].filename)
333 {
334 CloseDBInstance(&db_handles[i]);
335 }
336 }
337
338 DynamicDBHandles *db_dynamic_handles_list = db_dynamic_handles;
339 while (db_dynamic_handles_list)
340 {
341 DBHandle *handle = db_dynamic_handles_list->handle;
342 CloseDBInstance(handle);
343
344 DynamicDBHandles *next_free = db_dynamic_handles_list;
345 db_dynamic_handles_list = db_dynamic_handles_list->next;
346
347 FREE_AND_NULL(handle);
348 FREE_AND_NULL(next_free);
349 }
350 }
351
RegisterShutdownHandler(void)352 static void RegisterShutdownHandler(void)
353 {
354 RegisterCleanupFunction(&CloseAllDBExit);
355 }
356
357 /**
358 * Keeps track of the maximum number of concurrent transactions, which is
359 * expected to be set by agents as they start up. If it is not set it will use
360 * the existing value. If it is set, but the database cannot honor it, CFEngine
361 * will warn.
362 * @param max_txn Maximum number of concurrent transactions for a single
363 * database.
364 */
DBSetMaximumConcurrentTransactions(int max_txn)365 void DBSetMaximumConcurrentTransactions(int max_txn)
366 {
367 DBPrivSetMaximumConcurrentTransactions(max_txn);
368 }
369
370 static inline
OpenDBInstance(DBHandle ** dbp,dbid id,DBHandle * handle)371 bool OpenDBInstance(DBHandle **dbp, dbid id, DBHandle *handle)
372 {
373 assert(handle != NULL);
374
375 ThreadLock(&handle->lock);
376 if (handle->frozen)
377 {
378 Log(LOG_LEVEL_WARNING, "Attempt to open a frozen DB '%s'", handle->filename);
379 ThreadUnlock(&handle->lock);
380 return false;
381 }
382 if (handle->refcount == 0)
383 {
384 FileLock lock = EMPTY_FILE_LOCK;
385 if (DBPathLock(&lock, handle->filename))
386 {
387 handle->open_tstamp = time(NULL);
388 handle->priv = DBPrivOpenDB(handle->filename, id);
389
390 if (handle->priv == DB_PRIV_DATABASE_BROKEN)
391 {
392 DBPathMoveBroken(handle->filename);
393 handle->priv = DBPrivOpenDB(handle->filename, id);
394 if (handle->priv == DB_PRIV_DATABASE_BROKEN)
395 {
396 handle->priv = NULL;
397 }
398 }
399
400 DBPathUnLock(&lock);
401 }
402
403 if (handle->priv)
404 {
405 if (!DBMigrate(handle, id))
406 {
407 DBPrivCloseDB(handle->priv);
408 handle->priv = NULL;
409 handle->open_tstamp = -1;
410 }
411 }
412 }
413
414 if (handle->priv)
415 {
416 handle->refcount++;
417 *dbp = handle;
418
419 /* Only register shutdown handler if any database was opened
420 * correctly. Otherwise this shutdown caller may be called too early,
421 * and shutdown handler installed by the database library may end up
422 * being called before CloseAllDB function */
423
424 pthread_once(&db_shutdown_once, RegisterShutdownHandler);
425 }
426 else
427 {
428 *dbp = NULL;
429 }
430
431 ThreadUnlock(&handle->lock);
432 return *dbp != NULL;
433 }
434
OpenSubDB(DBHandle ** dbp,dbid id,const char * sub_name)435 bool OpenSubDB(DBHandle **dbp, dbid id, const char *sub_name)
436 {
437 DBHandle *handle = DBHandleGetSubDB(id, sub_name);
438 return OpenDBInstance(dbp, id, handle);
439 }
440
OpenDB(DBHandle ** dbp,dbid id)441 bool OpenDB(DBHandle **dbp, dbid id)
442 {
443 DBHandle *handle = DBHandleGet(id);
444 return OpenDBInstance(dbp, id, handle);
445 }
446
447 /**
448 * @db_file_name Absolute path of the DB file
449 */
GetDBHandleFromFilename(const char * db_file_name)450 DBHandle *GetDBHandleFromFilename(const char *db_file_name)
451 {
452 ThreadLock(&db_handles_lock);
453 for(dbid id=0; id < dbid_max; id++)
454 {
455 if (StringEqual(db_handles[id].filename, db_file_name))
456 {
457 ThreadUnlock(&db_handles_lock);
458 return &(db_handles[id]);
459 }
460 }
461 ThreadUnlock(&db_handles_lock);
462 return NULL;
463 }
464
465
GetDBOpenTimestamp(const DBHandle * handle)466 time_t GetDBOpenTimestamp(const DBHandle *handle)
467 {
468 assert(handle != NULL);
469 return handle->open_tstamp;
470 }
471
CloseDB(DBHandle * handle)472 void CloseDB(DBHandle *handle)
473 {
474 assert(handle != NULL);
475
476 /* Skip in case of nested locking, for example signal handler.
477 * DB behaviour becomes erratic otherwise (CFE-1996). */
478 ThreadLock(&handle->lock);
479 if (handle->frozen)
480 {
481 /* Just clean some allocated memory, but don't touch the DB itself. */
482 free(handle->filename);
483 free(handle->subname);
484 ThreadUnlock(&handle->lock);
485 return;
486 }
487 DBPrivCommit(handle->priv);
488
489 if (handle->refcount < 1)
490 {
491 Log(LOG_LEVEL_ERR,
492 "Trying to close database which is not open: %s",
493 handle->filename);
494 }
495 else
496 {
497 handle->refcount--;
498 if (handle->refcount == 0)
499 {
500 DBPrivCloseDB(handle->priv);
501 handle->open_tstamp = -1;
502 }
503 }
504
505 ThreadUnlock(&handle->lock);
506 }
507
CleanDB(DBHandle * handle)508 bool CleanDB(DBHandle *handle)
509 {
510 ThreadLock(&handle->lock);
511 if (handle->frozen)
512 {
513 Log(LOG_LEVEL_WARNING, "Attempt to clean a frozen DB '%s'", handle->filename);
514 ThreadUnlock(&handle->lock);
515 return false;
516 }
517 bool ret = DBPrivClean(handle->priv);
518 ThreadUnlock(&handle->lock);
519
520 return ret;
521 }
522
523 /**
524 * Freezes the DB so that it is never touched by this process again. In
525 * particular, new OpenDB() calls are ignored and CloseAllDBExit() also ignores
526 * the DB.
527 */
FreezeDB(DBHandle * handle)528 void FreezeDB(DBHandle *handle)
529 {
530 /* This is intentionally NOT using the handle->lock to avoid deadlocks.
531 * Nothing ever sets this to 'false' explicitly, that's only done in the
532 * initialization with '{ { 0 } }', so this bit-flip is safe. */
533 Log(LOG_LEVEL_NOTICE, "Freezing the DB '%s'", handle->filename);
534 handle->frozen = true;
535 }
536
537 /*****************************************************************************/
538
ReadComplexKeyDB(DBHandle * handle,const char * key,int key_size,void * dest,int dest_size)539 bool ReadComplexKeyDB(DBHandle *handle, const char *key, int key_size,
540 void *dest, int dest_size)
541 {
542 return DBPrivRead(handle->priv, key, key_size, dest, dest_size);
543 }
544
WriteComplexKeyDB(DBHandle * handle,const char * key,int key_size,const void * value,int value_size)545 bool WriteComplexKeyDB(DBHandle *handle, const char *key, int key_size,
546 const void *value, int value_size)
547 {
548 return DBPrivWrite(handle->priv, key, key_size, value, value_size);
549 }
550
DeleteComplexKeyDB(DBHandle * handle,const char * key,int key_size)551 bool DeleteComplexKeyDB(DBHandle *handle, const char *key, int key_size)
552 {
553 return DBPrivDelete(handle->priv, key, key_size);
554 }
555
ReadDB(DBHandle * handle,const char * key,void * dest,int destSz)556 bool ReadDB(DBHandle *handle, const char *key, void *dest, int destSz)
557 {
558 return DBPrivRead(handle->priv, key, strlen(key) + 1, dest, destSz);
559 }
560
WriteDB(DBHandle * handle,const char * key,const void * src,int srcSz)561 bool WriteDB(DBHandle *handle, const char *key, const void *src, int srcSz)
562 {
563 return DBPrivWrite(handle->priv, key, strlen(key) + 1, src, srcSz);
564 }
565
OverwriteDB(DBHandle * handle,const char * key,const void * value,size_t value_size,OverwriteCondition Condition,void * data)566 bool OverwriteDB(DBHandle *handle, const char *key, const void *value, size_t value_size,
567 OverwriteCondition Condition, void *data)
568 {
569 assert(handle != NULL);
570 return DBPrivOverwrite(handle->priv, key, strlen(key) + 1, value, value_size, Condition, data);
571 }
572
HasKeyDB(DBHandle * handle,const char * key,int key_size)573 bool HasKeyDB(DBHandle *handle, const char *key, int key_size)
574 {
575 return DBPrivHasKey(handle->priv, key, key_size);
576 }
577
ValueSizeDB(DBHandle * handle,const char * key,int key_size)578 int ValueSizeDB(DBHandle *handle, const char *key, int key_size)
579 {
580 return DBPrivGetValueSize(handle->priv, key, key_size);
581 }
582
DeleteDB(DBHandle * handle,const char * key)583 bool DeleteDB(DBHandle *handle, const char *key)
584 {
585 return DBPrivDelete(handle->priv, key, strlen(key) + 1);
586 }
587
NewDBCursor(DBHandle * handle,DBCursor ** cursor)588 bool NewDBCursor(DBHandle *handle, DBCursor **cursor)
589 {
590 DBCursorPriv *priv = DBPrivOpenCursor(handle->priv);
591 if (!priv)
592 {
593 return false;
594 }
595
596 *cursor = xcalloc(1, sizeof(DBCursor));
597 (*cursor)->cursor = priv;
598 return true;
599 }
600
NextDB(DBCursor * cursor,char ** key,int * ksize,void ** value,int * vsize)601 bool NextDB(DBCursor *cursor, char **key, int *ksize,
602 void **value, int *vsize)
603 {
604 return DBPrivAdvanceCursor(cursor->cursor, (void **)key, ksize, value, vsize);
605 }
606
DBCursorDeleteEntry(DBCursor * cursor)607 bool DBCursorDeleteEntry(DBCursor *cursor)
608 {
609 return DBPrivDeleteCursorEntry(cursor->cursor);
610 }
611
DBCursorWriteEntry(DBCursor * cursor,const void * value,int value_size)612 bool DBCursorWriteEntry(DBCursor *cursor, const void *value, int value_size)
613 {
614 return DBPrivWriteCursorEntry(cursor->cursor, value, value_size);
615 }
616
DeleteDBCursor(DBCursor * cursor)617 bool DeleteDBCursor(DBCursor *cursor)
618 {
619 DBPrivCloseCursor(cursor->cursor);
620 free(cursor);
621 return true;
622 }
623
DBPathLock(FileLock * lock,const char * filename)624 static bool DBPathLock(FileLock *lock, const char *filename)
625 {
626 char *filename_lock;
627 if (xasprintf(&filename_lock, "%s.lock", filename) == -1)
628 {
629 ProgrammingError("Unable to construct lock database filename for file %s", filename);
630 }
631
632 if (ExclusiveFileLockPath(lock, filename_lock, true) != 0)
633 {
634 Log(LOG_LEVEL_ERR, "Unable to lock database lock file '%s'.", filename_lock);
635 free(filename_lock);
636 return false;
637 }
638
639 free(filename_lock);
640
641 return true;
642 }
643
DBPathUnLock(FileLock * lock)644 static void DBPathUnLock(FileLock *lock)
645 {
646 ExclusiveFileUnlock(lock, true);
647 }
648
DBPathMoveBroken(const char * filename)649 static void DBPathMoveBroken(const char *filename)
650 {
651 char *filename_broken;
652 if (xasprintf(&filename_broken, "%s.broken", filename) == -1)
653 {
654 ProgrammingError("Unable to construct broken database filename for file '%s'", filename);
655 }
656
657 if(rename(filename, filename_broken) != 0)
658 {
659 Log(LOG_LEVEL_ERR, "Failed moving broken db out of the way '%s'", filename);
660 }
661
662 free(filename_broken);
663 }
664
LoadDatabaseToStringMap(dbid database_id)665 StringMap *LoadDatabaseToStringMap(dbid database_id)
666 {
667 CF_DB *db_conn = NULL;
668 CF_DBC *db_cursor = NULL;
669 char *key = NULL;
670 void *value = NULL;
671 int key_size = 0;
672 int value_size = 0;
673
674 if (!OpenDB(&db_conn, database_id))
675 {
676 return NULL;
677 }
678
679 if (!NewDBCursor(db_conn, &db_cursor))
680 {
681 Log(LOG_LEVEL_ERR, "Unable to scan db");
682 CloseDB(db_conn);
683 return NULL;
684 }
685
686 StringMap *db_map = StringMapNew();
687 while (NextDB(db_cursor, &key, &key_size, &value, &value_size))
688 {
689 if (!key)
690 {
691 continue;
692 }
693
694 if (!value)
695 {
696 Log(LOG_LEVEL_VERBOSE, "Invalid entry (key='%s') in database.", key);
697 continue;
698 }
699
700 void *val = xcalloc(1, value_size);
701 val = memcpy(val, value, value_size);
702
703 StringMapInsert(db_map, xstrdup(key), val);
704 }
705
706 DeleteDBCursor(db_cursor);
707 CloseDB(db_conn);
708
709 return db_map;
710 }
711
712 /**
713 * Checks if a DB repair flag file is present and if it is, removes it.
714 *
715 * @return Whether the DB repair flag file was present or not.
716 */
CheckDBRepairFlagFile()717 bool CheckDBRepairFlagFile()
718 {
719 /* The DB repair flag file can be created by user or by some process
720 * that hit an error condition potentially caused by local DB corruption
721 * that it was not able to handle properly by repairing the corrupted DB
722 * file(s). For example, if a process is killed by a signal. */
723 char repair_flag_file[PATH_MAX] = { 0 };
724 bool present = false;
725 xsnprintf(repair_flag_file, PATH_MAX, "%s%c%s",
726 GetStateDir(), FILE_SEPARATOR, CF_DB_REPAIR_TRIGGER);
727 /* This is full of race-conditions, but it's just a best-effort
728 * thing. If a force-repair is missed, it will happen next time. If it's
729 * done twice, no big deal. */
730 if (access(repair_flag_file, F_OK) == 0)
731 {
732 present = true;
733 unlink(repair_flag_file);
734 }
735 return present;
736 }
737