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