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 /*
26  * Implementation using Tokyo Cabinet hash API.
27  */
28 
29 #include <cf3.defs.h>
30 
31 #include <dbm_priv.h>
32 #include <logging.h>
33 #include <string_lib.h>
34 
35 #ifdef TCDB
36 
37 # include <tcutil.h>
38 # include <tchdb.h>
39 
40 struct DBPriv_
41 {
42     /*
43      * This mutex prevents destructive modifications of the database (removing
44      * records) while the cursor is active on it.
45      */
46     pthread_mutex_t cursor_lock;
47 
48     TCHDB *hdb;
49 };
50 
51 struct DBCursorPriv_
52 {
53     DBPriv *db;
54 
55     char *current_key;
56     int current_key_size;
57     char *curval;
58 
59     /*
60      * Removing a key underneath the active cursor stops the database iteration,
61      * so if key needs to be deleted while database is iterated, this fact is
62      * remembered and once iterator advances to next key, this pending delete is
63      * executed.
64      *
65      * Writes to key underneath the active cursor are safe, so only deletes are
66      * tracked.
67      */
68     bool pending_delete;
69 };
70 
71 /******************************************************************************/
72 
LockCursor(DBPriv * db)73 static bool LockCursor(DBPriv *db)
74 {
75     int ret = pthread_mutex_lock(&db->cursor_lock);
76     if (ret != 0)
77     {
78         errno = ret;
79         Log(LOG_LEVEL_ERR, "Unable to obtain cursor lock for Tokyo Cabinet database. (pthread_mutex_lock: %s)", GetErrorStr());
80         return false;
81     }
82     return true;
83 }
84 
UnlockCursor(DBPriv * db)85 static void UnlockCursor(DBPriv *db)
86 {
87     int ret = pthread_mutex_unlock(&db->cursor_lock);
88     if (ret != 0)
89     {
90         errno = ret;
91         Log(LOG_LEVEL_ERR, "Unable to release cursor lock for Tokyo Cabinet database. (pthread_mutex_unlock: %s)",
92             GetErrorStr());
93     }
94 }
95 
DBPrivGetFileExtension(void)96 const char *DBPrivGetFileExtension(void)
97 {
98     return "tcdb";
99 }
100 
ErrorMessage(TCHDB * hdb)101 static const char *ErrorMessage(TCHDB *hdb)
102 {
103     return tchdberrmsg(tchdbecode(hdb));
104 }
105 
OpenTokyoDatabase(const char * filename,TCHDB ** hdb)106 static bool OpenTokyoDatabase(const char *filename, TCHDB **hdb)
107 {
108     *hdb = tchdbnew();
109 
110     if (!tchdbsetmutex(*hdb))
111     {
112         return false;
113     }
114 
115     if (!tchdbopen(*hdb, filename, HDBOWRITER | HDBOCREAT))
116     {
117         return false;
118     }
119 
120     static int threshold = -1; /* GLOBAL_X */
121 
122     if (threshold == -1)
123     {
124         /**
125            Optimize always if TCDB_OPTIMIZE_PERCENT is equal to 100
126            Never optimize if  TCDB_OPTIMIZE_PERCENT is equal to 0
127          */
128         const char *perc = getenv("TCDB_OPTIMIZE_PERCENT");
129         if (perc != NULL)
130         {
131             /* Environment variable exists */
132             char *end;
133             long result = strtol(perc, &end, 10);
134 
135             /* Environment variable is a number and in 0..100 range */
136             if (!*end && result >-1 && result < 101)
137             {
138                threshold = 100 - (int)result;
139             }
140             else
141             {
142                 /* This corresponds to 1% */
143                 threshold = 99;
144             }
145         }
146         else
147         {
148             /* This corresponds to 1% */
149             threshold = 99;
150         }
151     }
152     if ((threshold != 100) && (threshold == 0 || (int)(rand()%threshold) == 0))
153     {
154         if (!tchdboptimize(*hdb, -1, -1, -1, false))
155         {
156             tchdbclose(*hdb);
157             return false;
158         }
159     }
160 
161     return true;
162 }
163 
DBPrivSetMaximumConcurrentTransactions(ARG_UNUSED int max_txn)164 void DBPrivSetMaximumConcurrentTransactions(ARG_UNUSED int max_txn)
165 {
166 }
167 
DBPrivOpenDB(const char * dbpath,ARG_UNUSED dbid id)168 DBPriv *DBPrivOpenDB(const char *dbpath, ARG_UNUSED dbid id)
169 {
170     DBPriv *db = xcalloc(1, sizeof(DBPriv));
171 
172     pthread_mutex_init(&db->cursor_lock, NULL);
173 
174     if (!OpenTokyoDatabase(dbpath, &db->hdb))
175     {
176         Log(LOG_LEVEL_ERR, "Could not open Tokyo database at path '%s'. (OpenTokyoDatabase: %s)",
177               dbpath, ErrorMessage(db->hdb));
178 
179         int errcode = tchdbecode(db->hdb);
180 
181         if(errcode != TCEMETA && errcode != TCEREAD)
182         {
183             goto err;
184         }
185 
186         tchdbdel(db->hdb);
187 
188         return DB_PRIV_DATABASE_BROKEN;
189     }
190 
191     return db;
192 
193 err:
194     pthread_mutex_destroy(&db->cursor_lock);
195     tchdbdel(db->hdb);
196     free(db);
197     return NULL;
198 }
199 
DBPrivCloseDB(DBPriv * db)200 void DBPrivCloseDB(DBPriv *db)
201 {
202     int ret;
203 
204     if ((ret = pthread_mutex_destroy(&db->cursor_lock)) != 0)
205     {
206         errno = ret;
207         Log(LOG_LEVEL_ERR, "Unable to destroy mutex during Tokyo Cabinet database handle close. (pthread_mutex_destroy: %s)",
208             GetErrorStr());
209     }
210 
211     if (!tchdbclose(db->hdb))
212     {
213         Log(LOG_LEVEL_ERR, "Closing database failed. (tchdbclose: %s)", ErrorMessage(db->hdb));
214     }
215 
216     tchdbdel(db->hdb);
217     free(db);
218 }
219 
DBPrivCommit(ARG_UNUSED DBPriv * db)220 void DBPrivCommit(ARG_UNUSED DBPriv *db)
221 {
222 }
223 
DBPrivClean(DBPriv * db)224 bool DBPrivClean(DBPriv *db)
225 {
226     DBCursorPriv *cursor = DBPrivOpenCursor(db);
227 
228     if (!cursor)
229     {
230         return false;
231     }
232 
233     void *key;
234     int key_size;
235     void *value;
236     int value_size;
237 
238     while ((DBPrivAdvanceCursor(cursor, &key, &key_size, &value, &value_size)))
239     {
240         DBPrivDeleteCursorEntry(cursor);
241     }
242 
243     DBPrivCloseCursor(cursor);
244 
245     return true;
246 }
247 
DBPrivHasKey(DBPriv * db,const void * key,int key_size)248 bool DBPrivHasKey(DBPriv *db, const void *key, int key_size)
249 {
250     // FIXME: distinguish between "entry not found" and "error occurred"
251 
252     return tchdbvsiz(db->hdb, key, key_size) != -1;
253 }
254 
DBPrivGetValueSize(DBPriv * db,const void * key,int key_size)255 int DBPrivGetValueSize(DBPriv *db, const void *key, int key_size)
256 {
257     return tchdbvsiz(db->hdb, key, key_size);
258 }
259 
DBPrivRead(DBPriv * db,const void * key,int key_size,void * dest,size_t dest_size)260 bool DBPrivRead(DBPriv *db, const void *key, int key_size, void *dest, size_t dest_size)
261 {
262     if (tchdbget3(db->hdb, key, key_size, dest, dest_size) == -1)
263     {
264         if (tchdbecode(db->hdb) != TCENOREC)
265         {
266             Log(LOG_LEVEL_ERR, "Could not read key '%s': (tchdbget3: %s)", (const char *)key, ErrorMessage(db->hdb));
267         }
268         return false;
269     }
270 
271     return true;
272 }
273 
Write(TCHDB * hdb,const void * key,int key_size,const void * value,int value_size)274 static bool Write(TCHDB *hdb, const void *key, int key_size, const void *value, int value_size)
275 {
276     if (!tchdbput(hdb, key, key_size, value, value_size))
277     {
278         Log(LOG_LEVEL_ERR, "Could not write key to Tokyo path '%s'. (tchdbput: %s)",
279               tchdbpath(hdb), ErrorMessage(hdb));
280         return false;
281     }
282     return true;
283 }
284 
Delete(TCHDB * hdb,const void * key,int key_size)285 static bool Delete(TCHDB *hdb, const void *key, int key_size)
286 {
287     if (!tchdbout(hdb, key, key_size) && tchdbecode(hdb) != TCENOREC)
288     {
289         Log(LOG_LEVEL_ERR, "Could not delete Tokyo key. (tchdbout: %s)",
290             ErrorMessage(hdb));
291         return false;
292     }
293 
294     return true;
295 }
296 
297 /*
298  * This one has to be locked against cursor, or interaction between
299  * write/pending delete might yield surprising results.
300  */
DBPrivWrite(DBPriv * db,const void * key,int key_size,const void * value,int value_size)301 bool DBPrivWrite(DBPriv *db, const void *key, int key_size, const void *value, int value_size)
302 {
303     /* FIXME: get a cursor and see what is the current key */
304 
305     int ret = Write(db->hdb, key, key_size, value, value_size);
306 
307     return ret;
308 }
309 
DBPrivOverwrite(DBPriv * db,const char * key,int key_size,const void * value,size_t value_size,OverwriteCondition Condition,void * data)310 bool DBPrivOverwrite(DBPriv *db, const char *key, int key_size, const void *value, size_t value_size,
311                      OverwriteCondition Condition, void *data)
312 {
313     ssize_t cur_val_size = tchdbvsiz(db->hdb, key, key_size);
314     void *cur_val = NULL;
315     bool exists = (cur_val_size > 0);
316 
317     if (exists)
318     {
319         assert(cur_val_size > 0);
320         cur_val = xmalloc(cur_val_size);
321         if (tchdbget3(db->hdb, key, key_size, cur_val, cur_val_size) == -1)
322         {
323             /* If exists, we should never get the TCENOREC error. */
324             assert(tchdbecode(db->hdb) != TCENOREC);
325 
326             Log(LOG_LEVEL_ERR, "Could not read key '%s': (tchdbget3: %s)", (const char *)key, ErrorMessage(db->hdb));
327             free(cur_val);
328             return false;
329         }
330     }
331     if ((Condition != NULL) && !Condition(cur_val, cur_val_size, data))
332     {
333         free(cur_val);
334         return false;
335     }
336     free(cur_val);
337 
338     return Write(db->hdb, key, key_size, value, value_size);
339 }
340 
341 /*
342  * This one has to be locked against cursor -- deleting entries might interrupt
343  * iteration.
344  */
DBPrivDelete(DBPriv * db,const void * key,int key_size)345 bool DBPrivDelete(DBPriv *db, const void *key, int key_size)
346 {
347     if (!LockCursor(db))
348     {
349         return false;
350     }
351 
352     int ret = Delete(db->hdb, key, key_size);
353 
354     UnlockCursor(db);
355     return ret;
356 }
357 
DBPrivOpenCursor(DBPriv * db)358 DBCursorPriv *DBPrivOpenCursor(DBPriv *db)
359 {
360     if (!LockCursor(db))
361     {
362         return false;
363     }
364 
365     DBCursorPriv *cursor = xcalloc(1, sizeof(DBCursorPriv));
366     cursor->db = db;
367 
368     /* Cursor remains locked */
369     return cursor;
370 }
371 
DBPrivAdvanceCursor(DBCursorPriv * cursor,void ** key,int * key_size,void ** value,int * value_size)372 bool DBPrivAdvanceCursor(DBCursorPriv *cursor, void **key, int *key_size,
373                      void **value, int *value_size)
374 {
375     *key = tchdbgetnext3(cursor->db->hdb,
376                          cursor->current_key, cursor->current_key_size,
377                          key_size, (const char **)value, value_size);
378 
379     /*
380      * If there is pending delete on the key, apply it
381      */
382     if (cursor->pending_delete)
383     {
384         Delete(cursor->db->hdb, cursor->current_key, cursor->current_key_size);
385     }
386 
387     /* This will free the value as well: tchdbgetnext3 returns single allocated
388      * chunk of memory */
389 
390     free(cursor->current_key);
391 
392     cursor->current_key = *key;
393     cursor->current_key_size = *key_size;
394     cursor->pending_delete = false;
395 
396     return *key != NULL;
397 }
398 
DBPrivDeleteCursorEntry(DBCursorPriv * cursor)399 bool DBPrivDeleteCursorEntry(DBCursorPriv *cursor)
400 {
401     cursor->pending_delete = true;
402     return true;
403 }
404 
DBPrivWriteCursorEntry(DBCursorPriv * cursor,const void * value,int value_size)405 bool DBPrivWriteCursorEntry(DBCursorPriv *cursor, const void *value, int value_size)
406 {
407     /*
408      * If a pending deletion of entry has been requested, cancel it
409      */
410     cursor->pending_delete = false;
411 
412     return Write(cursor->db->hdb, cursor->current_key, cursor->current_key_size,
413                  value, value_size);
414 }
415 
DBPrivCloseCursor(DBCursorPriv * cursor)416 void DBPrivCloseCursor(DBCursorPriv *cursor)
417 {
418     DBPriv *db = cursor->db;
419 
420     if (cursor->pending_delete)
421     {
422         Delete(db->hdb, cursor->current_key, cursor->current_key_size);
423     }
424 
425     free(cursor->current_key);
426     free(cursor);
427 
428     /* Cursor lock was obtained in DBPrivOpenCursor */
429     UnlockCursor(db);
430 }
431 
432 
DBPrivDiagnose(const char * dbpath)433 char *DBPrivDiagnose(const char *dbpath)
434 {
435 #define SWAB64(num) \
436     ( \
437         ((num & 0x00000000000000ffULL) << 56) | \
438         ((num & 0x000000000000ff00ULL) << 40) | \
439         ((num & 0x0000000000ff0000ULL) << 24) | \
440         ((num & 0x00000000ff000000ULL) << 8) | \
441         ((num & 0x000000ff00000000ULL) >> 8) | \
442         ((num & 0x0000ff0000000000ULL) >> 24) | \
443         ((num & 0x00ff000000000000ULL) >> 40) | \
444         ((num & 0xff00000000000000ULL) >> 56) \
445     )
446 
447     static const char *const MAGIC="ToKyO CaBiNeT";
448 
449     FILE *fp = fopen(dbpath, "r");
450     if(!fp)
451     {
452         return StringFormat("Error opening file '%s': %s", dbpath, strerror(errno));
453     }
454 
455     if(fseek(fp, 0, SEEK_END) != 0)
456     {
457         fclose(fp);
458         return StringFormat("Error seeking to end: %s\n", strerror(errno));
459     }
460 
461     long size = ftell(fp);
462     if(size < 256)
463     {
464         fclose(fp);
465         return StringFormat("Seek-to-end size less than minimum required: %ld", size);
466     }
467 
468     char hbuf[256];
469     memset(hbuf, 0, sizeof(hbuf));
470 
471     if(fseek(fp, 0, SEEK_SET) != 0)
472     {
473         fclose(fp);
474         return StringFormat("Error seeking to offset 256: %s", strerror(errno));
475     }
476 
477     if(fread(&hbuf, 256, 1, fp) != 1)
478     {
479         fclose(fp);
480         return StringFormat("Error reading 256 bytes: %s\n", strerror(errno));
481     }
482     fclose(fp);
483 
484     if(strncmp(hbuf, MAGIC, strlen(MAGIC)) != 0)
485     {
486         return StringFormat("Magic string mismatch");
487     }
488 
489     uint64_t declared_size = 0;
490     /* Read file size from tchdb header. It is stored in little endian. */
491     memcpy(&declared_size, &hbuf[56], sizeof(uint64_t));
492     if (declared_size == (uint64_t) size)
493     {
494         return NULL; // all is well
495     }
496     else
497     {
498         declared_size = SWAB64(declared_size);
499         if (declared_size == (uint64_t) size)
500         {
501             return StringFormat("Endianness mismatch, declared size SWAB64 '%ju' equals seek-to-end size '%ld'", (uintmax_t) declared_size, size);
502         }
503         else
504         {
505             return StringFormat("Size mismatch, declared size SWAB64 '%ju', seek-to-end-size '%ld'", (uintmax_t) declared_size, size);
506         }
507     }
508 }
509 
510 #endif
511