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