]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/dbif.c
New utility to maintain dbd databases: dbd. Also replaces cnid_index. Still incomplete.
[netatalk.git] / etc / cnid_dbd / dbif.c
1 /*
2  * $Id: dbif.c,v 1.7 2009-04-28 13:01:24 franklahm Exp $
3  *
4  * Copyright (C) Joerg Lenneis 2003
5  * Copyright (C) Frank Lahm 2009
6  * All Rights Reserved.  See COPYING.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif /* HAVE_CONFIG_H */
12
13 #include <stdio.h>
14 #include <errno.h>
15 #include <stdlib.h>
16 #ifdef HAVE_SYS_TYPES_H
17 #include <sys/types.h>
18 #endif /* HAVE_SYS_TYPES_H */
19 #include <string.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/cdefs.h>
23 #include <unistd.h>
24 #include <atalk/logger.h>
25 #include <db.h>
26 #include "db_param.h"
27 #include "dbif.h"
28 #include "pack.h"
29
30 #define DB_ERRLOGFILE "db_errlog"
31
32 static DB_ENV *db_env = NULL;
33 static DB_TXN *db_txn = NULL;
34 static FILE   *db_errlog = NULL;
35
36 static struct db_table {
37     char            *name;
38     DB              *db;
39     u_int32_t       general_flags;
40     DBTYPE          type;
41 } db_table[] =
42 {
43     { "cnid2.db",       NULL,      0, DB_BTREE},
44     { "devino.db",      NULL,      0, DB_BTREE},
45     { "didname.db",     NULL,      0, DB_BTREE},
46 };
47
48 static char *old_dbfiles[] = {"cnid.db", NULL};
49
50 /* --------------- */
51 static int  db_compat_associate (DB *p, DB *s,
52                                  int (*callback)(DB *, const DBT *,const DBT *, DBT *),
53                                  u_int32_t flags)
54 {
55     return p->associate(p, NULL, s, callback, flags);
56 }
57
58 /* --------------- */
59 static int db_compat_open(DB *db, char *file, char *name, DBTYPE type, int mode)
60 {
61     int ret;
62
63     ret = db->open(db, NULL, file, name, type, DB_CREATE, mode);
64
65     if (ret) {
66         LOG(log_error, logtype_cnid, "error opening database %s: %s", name, db_strerror(ret));
67         return -1;
68     } else {
69         return 0;
70     }
71 }
72
73 /* --------------- */
74 static int upgrade_required()
75 {
76     int i;
77     int found = 0;
78     struct stat st;
79
80     for (i = 0; old_dbfiles[i] != NULL; i++) {
81         if ( !(stat(old_dbfiles[i], &st) < 0) ) {
82             found++;
83             continue;
84         }
85         if (errno != ENOENT) {
86             LOG(log_error, logtype_cnid, "cnid_open: Checking %s gave %s", old_dbfiles[i], strerror(errno));
87             found++;
88         }
89     }
90     return found;
91 }
92
93 /* --------------- */
94 int dbif_stamp(void *buffer, int size)
95 {
96     struct stat st;
97     int         rc;
98
99     if (size < 8)
100         return -1;
101
102     if ((rc = stat(db_table[0].name, &st)) < 0) {
103         LOG(log_error, logtype_cnid, "error stating database %s: %s", db_table[0].name, db_strerror(rc));
104         return -1;
105     }
106     memset(buffer, 0, size);
107     memcpy(buffer, &st.st_ctime, sizeof(st.st_ctime));
108
109     return 0;
110 }
111
112 /* --------------- */
113 /*
114  *  We assume our current directory is already the BDB homedir. Otherwise
115  *  opening the databases will not work as expected.
116  */
117 int dbif_env_init(struct db_param *dbp, uint32_t dbenv_oflags)
118 {
119     int ret;
120     char **logfiles = NULL;
121     char **file;
122
123     /* Refuse to do anything if this is an old version of the CNID database */
124     if (upgrade_required()) {
125         LOG(log_error, logtype_cnid, "Found version 1 of the CNID database. Please upgrade to version 2");
126         return -1;
127     }
128
129     if ((db_errlog = fopen(DB_ERRLOGFILE, "a")) == NULL)
130         LOG(log_warning, logtype_cnid, "error creating/opening DB errlogfile: %s", strerror(errno));
131
132     if ((ret = db_env_create(&db_env, 0))) {
133         LOG(log_error, logtype_cnid, "error creating DB environment: %s",
134             db_strerror(ret));
135         db_env = NULL;
136         return -1;
137     }
138
139     if (db_errlog != NULL) {
140         db_env->set_errfile(db_env, db_errlog);
141         db_env->set_msgfile(db_env, db_errlog);
142     }
143
144     db_env->set_verbose(db_env, DB_VERB_RECOVERY, 1);
145
146     /* Open the database for recovery using DB_PRIVATE option which is faster */
147     if ((ret = db_env->open(db_env, ".", dbenv_oflags | DB_PRIVATE | DB_RECOVER, 0))) {
148         LOG(log_error, logtype_cnid, "error opening DB environment: %s",
149             db_strerror(ret));
150         db_env->close(db_env, 0);
151         db_env = NULL;
152         return -1;
153     }
154
155     if (db_errlog != NULL)
156         fflush(db_errlog);
157
158     if ((ret = db_env->close(db_env, 0))) {
159         LOG(log_error, logtype_cnid, "error closing DB environment after recovery: %s",
160             db_strerror(ret));
161         db_env = NULL;
162         return -1;
163     }
164
165     if ((ret = db_env_create(&db_env, 0))) {
166         LOG(log_error, logtype_cnid, "error creating DB environment after recovery: %s",
167             db_strerror(ret));
168         db_env = NULL;
169         return -1;
170     }
171
172     if ((ret = db_env->set_cachesize(db_env, 0, 1024 * dbp->cachesize, 0))) {
173         LOG(log_error, logtype_cnid, "error setting DB environment cachesize to %i: %s",
174             dbp->cachesize, db_strerror(ret));
175         db_env->close(db_env, 0);
176         db_env = NULL;
177         return -1;
178     }
179
180     if (db_errlog != NULL) {
181         db_env->set_errfile(db_env, db_errlog);
182         db_env->set_msgfile(db_env, db_errlog);
183     }
184     if ((ret = db_env->open(db_env, ".", dbenv_oflags, 0))) {
185         LOG(log_error, logtype_cnid, "error opening DB environment after recovery: %s",
186             db_strerror(ret));
187         db_env->close(db_env, 0);
188         db_env = NULL;
189         return -1;
190     }
191
192     if ((ret = db_env->set_flags(db_env, DB_AUTO_COMMIT, 1))) {
193         LOG(log_error, logtype_cnid, "error setting DB_AUTO_COMMIT flag: %s",
194             db_strerror(ret));
195         db_env->close(db_env, 0);
196         db_env = NULL;
197         return -1;
198     }
199
200     if (dbp->logfile_autoremove) {
201         if (db_env->log_archive(db_env, &logfiles, 0)) {
202             LOG(log_error, logtype_cnid, "error getting list of stale logfiles: %s",
203                 db_strerror(ret));
204             db_env->close(db_env, 0);
205             db_env = NULL;
206             return -1;
207         }
208         if (logfiles != NULL) {
209             for (file = logfiles; *file != NULL; file++) {
210                 if (unlink(*file) < 0)
211                     LOG(log_warning, logtype_cnid, "Error removing stale logfile %s: %s", *file, strerror(errno));
212             }
213             free(logfiles);
214         }
215
216 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7)
217         if ((ret = db_env->log_set_config(db_env, DB_LOG_AUTO_REMOVE, 1))) {
218             LOG(log_error, logtype_cnid, "error setting DB_LOG_AUTO_REMOVE flag: %s",
219             db_strerror(ret));
220             db_env->close(db_env, 0);
221             db_env = NULL;
222             return -1;
223         }
224 #else
225         if ((ret = db_env->set_flags(db_env, DB_LOG_AUTOREMOVE, 1))) {
226             LOG(log_error, logtype_cnid, "error setting DB_LOG_AUTOREMOVE flag: %s",
227                 db_strerror(ret));
228             db_env->close(db_env, 0);
229             db_env = NULL;
230             return -1;
231         }
232 #endif
233     }
234
235     return 0;
236 }
237
238 /* --------------- */
239 int dbif_open(struct db_param *dbp _U_, int do_truncate)
240 {
241     int ret;
242     int i;
243     u_int32_t count;
244
245     for (i = 0; i != DBIF_DB_CNT; i++) {
246         if ((ret = db_create(&(db_table[i].db), db_env, 0))) {
247             LOG(log_error, logtype_cnid, "error creating handle for database %s: %s",
248                 db_table[i].name, db_strerror(ret));
249             return -1;
250         }
251
252         if (db_table[i].general_flags) {
253             if ((ret = db_table[i].db->set_flags(db_table[i].db, db_table[i].general_flags))) {
254                 LOG(log_error, logtype_cnid, "error setting flags for database %s: %s",
255                     db_table[i].name, db_strerror(ret));
256                 return -1;
257             }
258         }
259
260         if (db_compat_open(db_table[i].db, db_table[0].name, db_table[i].name, db_table[i].type, 0664) < 0)
261             return -1;
262         if (db_errlog != NULL)
263             db_table[i].db->set_errfile(db_table[i].db, db_errlog);
264
265         if (do_truncate && i > 0) {
266             LOG(log_info, logtype_cnid, "Truncating CNID index.");
267             if ((ret = db_table[i].db->truncate(db_table[i].db, NULL, &count, 0))) {
268                 LOG(log_error, logtype_cnid, "error truncating database %s: %s",
269                     db_table[i].name, db_strerror(ret));
270                 return -1;
271             }
272         }
273     }
274
275     /* TODO: Implement CNID DB versioning info on new databases. */
276
277     /* Associate the secondary with the primary. */
278     LOG(log_debug, logtype_cnid, "Associating DBIF_IDX_DIDNAME index. Possibly reindexing...");
279     if ((ret = db_compat_associate(db_table[0].db, db_table[DBIF_IDX_DIDNAME].db, didname, (do_truncate)?DB_CREATE:0)) != 0) {
280         LOG(log_error, logtype_cnid, "Failed to associate didname database: %s",db_strerror(ret));
281         return -1;
282     }
283     LOG(log_debug, logtype_cnid, "... done.");
284
285     LOG(log_debug, logtype_cnid, "Associating DBIF_IDX_DEVINO index. Possibly reindexing...");
286     if ((ret = db_compat_associate(db_table[0].db, db_table[DBIF_IDX_DEVINO].db, devino, (do_truncate)?DB_CREATE:0)) != 0) {
287         LOG(log_error, logtype_cnid, "Failed to associate devino database: %s",db_strerror(ret));
288         return -1;
289     }
290     LOG(log_debug, logtype_cnid, "... done.");
291
292     return 0;
293 }
294
295 /* ------------------------ */
296 int dbif_closedb()
297 {
298     int i;
299     int ret;
300     int err = 0;
301
302     for (i = DBIF_DB_CNT -1; i >= 0; i--) {
303         if (db_table[i].db != NULL && (ret = db_table[i].db->close(db_table[i].db, 0))) {
304             LOG(log_error, logtype_cnid, "error closing database %s: %s", db_table[i].name, db_strerror(ret));
305             err++;
306         }
307     }
308     if (err)
309         return -1;
310     return 0;
311 }
312
313 /* ------------------------ */
314 int dbif_close()
315 {
316     int ret;
317     int err = 0;
318
319     if (dbif_closedb())
320         err++;
321
322     if (db_env != NULL && (ret = db_env->close(db_env, 0))) {
323         LOG(log_error, logtype_cnid, "error closing DB environment: %s", db_strerror(ret));
324         err++;
325     }
326     if (db_errlog != NULL && fclose(db_errlog) == EOF) {
327         LOG(log_error, logtype_cnid, "error closing DB logfile: %s", strerror(errno));
328         err++;
329     }
330     if (err)
331         return -1;
332     return 0;
333 }
334
335 /*
336  *  The following three functions are wrappers for DB->get(), DB->put() and DB->del().
337  *  All three return -1 on error. dbif_get()/dbif_del return 1 if the key was found and 0
338  *  otherwise. dbif_put() returns 0 if key/val was successfully updated and 1 if
339  *  the DB_NOOVERWRITE flag was specified and the key already exists.
340  *
341  *  All return codes other than DB_NOTFOUND and DB_KEYEXIST from the DB->()
342  *  functions are not expected and therefore error conditions.
343  */
344
345 int dbif_get(const int dbi, DBT *key, DBT *val, u_int32_t flags)
346 {
347     int ret;
348     DB *db = db_table[dbi].db;
349
350     ret = db->get(db, db_txn, key, val, flags);
351
352     if (ret == DB_NOTFOUND)
353         return 0;
354     if (ret) {
355         LOG(log_error, logtype_cnid, "error retrieving value from %s: %s", db_table[dbi].name, db_strerror(errno));
356         return -1;
357     } else
358         return 1;
359 }
360
361 /* search by secondary return primary */
362 int dbif_pget(const int dbi, DBT *key, DBT *pkey, DBT *val, u_int32_t flags)
363 {
364     int ret;
365     DB *db = db_table[dbi].db;
366
367     ret = db->pget(db, db_txn, key, pkey, val, flags);
368
369     if (ret == DB_NOTFOUND || ret == DB_SECONDARY_BAD) {
370         return 0;
371     }
372     if (ret) {
373         LOG(log_error, logtype_cnid, "error retrieving value from %s: %s", db_table[dbi].name, db_strerror(errno));
374         return -1;
375    } else
376         return 1;
377 }
378
379 /* -------------------------- */
380 int dbif_put(const int dbi, DBT *key, DBT *val, u_int32_t flags)
381 {
382     int ret;
383     DB *db = db_table[dbi].db;
384
385     if (dbif_txn_begin() < 0) {
386         LOG(log_error, logtype_cnid, "error setting key/value in %s: %s", db_table[dbi].name, db_strerror(errno));
387         return -1;
388     }
389
390     ret = db->put(db, db_txn, key, val, flags);
391
392     if (ret) {
393         if ((flags & DB_NOOVERWRITE) && ret == DB_KEYEXIST) {
394             return 1;
395         } else {
396             LOG(log_error, logtype_cnid, "error setting key/value in %s: %s", db_table[dbi].name, db_strerror(errno));
397             return -1;
398         }
399     } else
400         return 0;
401 }
402
403 int dbif_del(const int dbi, DBT *key, u_int32_t flags)
404 {
405     int ret;
406     DB *db = db_table[dbi].db;
407
408     if (dbif_txn_begin() < 0) {
409         LOG(log_error, logtype_cnid, "error deleting key/value from %s: %s", db_table[dbi].name, db_strerror(errno));
410         return -1;
411     }
412
413     ret = db->del(db, db_txn, key, flags);
414
415     if (ret == DB_NOTFOUND || ret == DB_SECONDARY_BAD)
416         return 0;
417     if (ret) {
418         LOG(log_error, logtype_cnid, "error deleting key/value from %s: %s", db_table[dbi].name, db_strerror(errno));
419         return -1;
420     } else
421         return 1;
422 }
423
424 int dbif_txn_begin()
425 {
426     int ret;
427
428     /* If we already have a acitve txn, just return */
429     if (db_txn)
430         return 0;
431
432     ret = db_env->txn_begin(db_env, NULL, &db_txn, 0);
433
434     if (ret) {
435         LOG(log_error, logtype_cnid, "error starting transaction: %s", db_strerror(errno));
436         return -1;
437     } else
438         return 0;
439 }
440
441 int dbif_txn_commit()
442 {
443     int ret;
444
445     if (!db_txn)
446         return 0;
447
448     ret = db_txn->commit(db_txn, 0);
449     db_txn = NULL;
450     
451     if (ret) {
452         LOG(log_error, logtype_cnid, "error committing transaction: %s", db_strerror(errno));
453         return -1;
454     } else
455         return 1;
456 }
457
458 int dbif_txn_abort()
459 {
460     int ret;
461
462     if (!db_txn)
463         return 0;
464
465     ret = db_txn->abort(db_txn);
466     db_txn = NULL;
467     
468     if (ret) {
469         LOG(log_error, logtype_cnid, "error aborting transaction: %s", db_strerror(errno));
470         return -1;
471     } else
472         return 0;
473 }
474
475 int dbif_txn_checkpoint(u_int32_t kbyte, u_int32_t min, u_int32_t flags)
476 {
477     int ret;
478     ret = db_env->txn_checkpoint(db_env, kbyte, min, flags);
479     if (ret) {
480         LOG(log_error, logtype_cnid, "error checkpointing transaction susystem: %s", db_strerror(errno));
481         return -1;
482     } else
483         return 0;
484 }
485
486 int dbif_count(const int dbi, u_int32_t *count)
487 {
488     int ret;
489     DB_BTREE_STAT *sp;
490     DB *db = db_table[dbi].db;
491
492     ret = db->stat(db, NULL, &sp, 0);
493
494     if (ret) {
495         LOG(log_error, logtype_cnid, "error getting stat infotmation on database: %s", db_strerror(errno));
496         return -1;
497     }
498
499     *count = sp->bt_ndata;
500     free(sp);
501
502     return 0;
503 }
504
505 int dbif_dump(int dumpindexes)
506 {
507     int rc;
508     uint32_t max = 0, count = 0, cnid, type, did;
509     uint64_t dev, ino;
510     DBC *cur;
511     DBT key = { 0 }, data = { 0 };
512     DB *db = db_table[DBIF_IDX_CNID].db;
513     char *typestring[2] = {"f", "d"};
514
515     printf("CNID database:\n");
516
517     rc = db->cursor(db, NULL, &cur, 0);
518     if (rc) {
519         LOG(log_error, logtype_cnid, "Couldn't create cursor: %s", db_strerror(errno));
520         return -1;
521     }
522
523     cur->c_get(cur, &key, &data, DB_FIRST);
524     while (rc == 0) {
525         /* Parse and print data */
526         memcpy(&cnid, key.data, 4);
527         cnid = ntohl(cnid);
528         if (cnid > max)
529             max = cnid;
530
531         /* Rootinfo node ? */
532         if (cnid == 0) {
533         } else {
534             /* dev */
535             memcpy(&dev, data.data + CNID_DEV_OFS, 8);
536             dev = ntoh64(dev);
537             /* ino */
538             memcpy(&ino, data.data + CNID_INO_OFS, 8);
539             ino = ntoh64(ino);
540             /* type */
541             memcpy(&type, data.data + CNID_TYPE_OFS, 4);
542             type = ntohl(type);
543             /* did */
544             memcpy(&did, data.data + CNID_DID_OFS, 4);
545             did = ntohl(did);
546
547             count++;
548             printf("id: %10u, did: %10u, type: %s, dev: 0x%llx, ino: 0x%016llx, name: %s\n", 
549                    cnid, did, typestring[type],
550                    (long long unsigned int)dev, (long long unsigned int)ino, 
551                    (char *)data.data + CNID_NAME_OFS);
552
553         }
554
555         rc = cur->c_get(cur, &key, &data, DB_NEXT);
556     }
557
558     if (rc != DB_NOTFOUND) {
559         LOG(log_error, logtype_cnid, "Error iterating over btree: %s", db_strerror(errno));
560         return -1;
561     }
562
563     rc = cur->c_close(cur);
564     if (rc) {
565         LOG(log_error, logtype_cnid, "Couldn't close cursor: %s", db_strerror(errno));
566         return -1;
567     }
568     printf("%u CNIDs in database. Max CNID: %u.\n", count, max);
569
570     /* Dump indexes too ? */
571     if (dumpindexes) {
572         /* DBIF_IDX_DEVINO */
573         printf("\ndev/inode index:\n");
574         count = 0;
575         db = db_table[DBIF_IDX_DEVINO].db;
576         rc = db->cursor(db, NULL, &cur, 0);
577         if (rc) {
578             LOG(log_error, logtype_cnid, "Couldn't create cursor: %s", db_strerror(errno));
579             return -1;
580         }
581         
582         cur->c_get(cur, &key, &data, DB_FIRST);
583         while (rc == 0) {
584             /* Parse and print data */
585
586             /* cnid */
587             memcpy(&cnid, data.data, CNID_LEN);
588             cnid = ntohl(cnid);
589             if (cnid == 0) {
590                 /* Rootinfo node */
591             } else {
592                 /* dev */
593                 memcpy(&dev, key.data, CNID_DEV_LEN);
594                 dev = ntoh64(dev);
595                 /* ino */
596                 memcpy(&ino, key.data + CNID_DEV_LEN, CNID_INO_LEN);
597                 ino = ntoh64(ino);
598                 
599                 printf("id: %10u <== dev: 0x%llx, ino: 0x%016llx\n", 
600                        cnid, (unsigned long long int)dev, (unsigned long long int)ino);
601                 count++;
602             }
603             rc = cur->c_get(cur, &key, &data, DB_NEXT);
604         }
605         if (rc != DB_NOTFOUND) {
606             LOG(log_error, logtype_cnid, "Error iterating over btree: %s", db_strerror(errno));
607             return -1;
608         }
609         
610         rc = cur->c_close(cur);
611         if (rc) {
612             LOG(log_error, logtype_cnid, "Couldn't close cursor: %s", db_strerror(errno));
613             return -1;
614         }
615         printf("%u items\n", count);
616
617         /* Now dump DBIF_IDX_DIDNAME */
618         printf("\ndid/name index:\n");
619         count = 0;
620         db = db_table[DBIF_IDX_DIDNAME].db;
621         rc = db->cursor(db, NULL, &cur, 0);
622         if (rc) {
623             LOG(log_error, logtype_cnid, "Couldn't create cursor: %s", db_strerror(errno));
624             return -1;
625         }
626         
627         cur->c_get(cur, &key, &data, DB_FIRST);
628         while (rc == 0) {
629             /* Parse and print data */
630
631             /* cnid */
632             memcpy(&cnid, data.data, CNID_LEN);
633             cnid = ntohl(cnid);
634             if (cnid == 0) {
635                 /* Rootinfo node */
636             } else {
637                 /* did */
638                 memcpy(&did, key.data, CNID_LEN);
639                 did = ntohl(did);
640
641                 printf("id: %10u <== did: %10u, name: %s\n", cnid, did, (char *)key.data + CNID_LEN);
642                 count++;
643             }
644             rc = cur->c_get(cur, &key, &data, DB_NEXT);
645         }
646         if (rc != DB_NOTFOUND) {
647             LOG(log_error, logtype_cnid, "Error iterating over btree: %s", db_strerror(errno));
648             return -1;
649         }
650         
651         rc = cur->c_close(cur);
652         if (rc) {
653             LOG(log_error, logtype_cnid, "Couldn't close cursor: %s", db_strerror(errno));
654             return -1;
655         }
656         printf("%u items\n", count);
657     }
658
659     return 0;
660 }
661