1 : #include "config.h"
2 :
3 : #ifdef OLD_DB_COMPAT
4 :
5 : #include "db.hh"
6 : #include "util.hh"
7 : #include "pathlocks.hh"
8 :
9 : #include <sys/types.h>
10 : #include <sys/stat.h>
11 : #include <fcntl.h>
12 : #include <errno.h>
13 :
14 : #include <memory>
15 :
16 : #include <db_cxx.h>
17 :
18 :
19 : namespace nix {
20 :
21 :
22 : /* Wrapper class to ensure proper destruction. */
23 : class DestroyDbc
24 : {
25 : Dbc * dbc;
26 : public:
27 0 : DestroyDbc(Dbc * _dbc) : dbc(_dbc) { }
28 0 : ~DestroyDbc() { dbc->close(); /* close() frees dbc */ }
29 : };
30 :
31 :
32 : class DestroyDbEnv
33 : {
34 : DbEnv * dbenv;
35 : public:
36 0 : DestroyDbEnv(DbEnv * _dbenv) : dbenv(_dbenv) { }
37 0 : ~DestroyDbEnv() {
38 0 : if (dbenv) {
39 0 : if (dbenv->get_DB_ENV()) dbenv->close(0);
40 0 : delete dbenv;
41 : }
42 0 : }
43 0 : void release() { dbenv = 0; };
44 : };
45 :
46 :
47 0 : static void rethrow(DbException & e)
48 : {
49 0 : throw Error(e.what());
50 : }
51 :
52 :
53 0 : Transaction::Transaction()
54 0 : : txn(0)
55 : {
56 0 : }
57 :
58 :
59 0 : Transaction::Transaction(Database & db)
60 0 : : txn(0)
61 : {
62 0 : begin(db);
63 0 : }
64 :
65 :
66 0 : Transaction::~Transaction()
67 : {
68 0 : if (txn) abort();
69 0 : }
70 :
71 :
72 0 : void Transaction::begin(Database & db)
73 : {
74 0 : assert(txn == 0);
75 0 : db.requireEnv();
76 : try {
77 0 : db.env->txn_begin(0, &txn, 0);
78 0 : } catch (DbException e) { rethrow(e); }
79 0 : }
80 :
81 :
82 0 : void Transaction::commit()
83 : {
84 0 : if (!txn) throw Error("commit called on null transaction");
85 0 : debug(format("committing transaction %1%") % (void *) txn);
86 0 : DbTxn * txn2 = txn;
87 0 : txn = 0;
88 : try {
89 0 : txn2->commit(0);
90 0 : } catch (DbException e) { rethrow(e); }
91 0 : }
92 :
93 :
94 0 : void Transaction::abort()
95 : {
96 0 : if (!txn) throw Error("abort called on null transaction");
97 0 : debug(format("aborting transaction %1%") % (void *) txn);
98 0 : DbTxn * txn2 = txn;
99 0 : txn = 0;
100 : try {
101 0 : txn2->abort();
102 0 : } catch (DbException e) { rethrow(e); }
103 0 : }
104 :
105 :
106 0 : void Transaction::moveTo(Transaction & t)
107 : {
108 0 : if (t.txn) throw Error("target txn already exists");
109 0 : t.txn = txn;
110 0 : txn = 0;
111 0 : }
112 :
113 :
114 0 : void Database::requireEnv()
115 : {
116 0 : checkInterrupt();
117 0 : if (!env) throw Error("database environment is not open "
118 0 : "(maybe you don't have sufficient permission?)");
119 0 : }
120 :
121 :
122 0 : Db * Database::getDb(TableId table)
123 : {
124 0 : if (table == 0)
125 : throw Error("database table is not open "
126 0 : "(maybe you don't have sufficient permission?)");
127 0 : std::map<TableId, Db *>::iterator i = tables.find(table);
128 0 : if (i == tables.end())
129 0 : throw Error("unknown table id");
130 0 : return i->second;
131 : }
132 :
133 :
134 0 : Database::Database()
135 : : env(0)
136 0 : , nextId(1)
137 : {
138 0 : }
139 :
140 :
141 0 : Database::~Database()
142 : {
143 0 : close();
144 0 : }
145 :
146 :
147 0 : void openEnv(DbEnv * & env, const string & path, u_int32_t flags)
148 : {
149 : try {
150 0 : createDirs(path);
151 0 : } catch (SysError & e) {
152 0 : if (e.errNo == EPERM || e.errNo == EACCES)
153 0 : throw DbNoPermission(format("cannot create the Nix database in `%1%'") % path);
154 : else
155 0 : throw;
156 : }
157 :
158 : try {
159 : env->open(path.c_str(),
160 : DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN |
161 : DB_CREATE | flags,
162 0 : 0666);
163 0 : } catch (DbException & e) {
164 0 : printMsg(lvlError, format("environment open failed: %1%") % e.what());
165 0 : throw;
166 : }
167 0 : }
168 :
169 :
170 0 : static int my_fsync(int fd)
171 : {
172 0 : return 0;
173 : }
174 :
175 :
176 0 : static void errorPrinter(const DbEnv * env, const char * errpfx, const char * msg)
177 : {
178 0 : printMsg(lvlError, format("Berkeley DB error: %1%") % msg);
179 0 : }
180 :
181 :
182 0 : static void messagePrinter(const DbEnv * env, const char * msg)
183 : {
184 0 : printMsg(lvlError, format("Berkeley DB message: %1%") % msg);
185 0 : }
186 :
187 :
188 0 : void Database::open2(const string & path, bool removeOldEnv)
189 : {
190 0 : if (env) throw Error(format("environment already open"));
191 :
192 0 : debug(format("opening database environment"));
193 :
194 :
195 : /* Create the database environment object. */
196 0 : DbEnv * env = new DbEnv(0);
197 0 : DestroyDbEnv deleteEnv(env);
198 :
199 0 : env->set_errcall(errorPrinter);
200 0 : env->set_msgcall(messagePrinter);
201 0 : if (getEnv("NIX_DEBUG_DB_REGISTER") == "1")
202 0 : env->set_verbose(DB_VERB_REGISTER, 1);
203 0 : env->set_verbose(DB_VERB_RECOVERY, 1);
204 :
205 : /* Smaller log files. */
206 0 : env->set_lg_bsize(32 * 1024); /* default */
207 0 : env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */
208 :
209 : /* Write the log, but don't sync. This protects transactions
210 : against application crashes, but if the system crashes, some
211 : transactions may be undone. An acceptable risk, I think. */
212 0 : env->set_flags(DB_TXN_WRITE_NOSYNC | DB_LOG_AUTOREMOVE, 1);
213 :
214 : /* Increase the locking limits. If you ever get `Dbc::get: Cannot
215 : allocate memory' or similar, especially while running
216 : `nix-store --verify', just increase the following number, then
217 : run db_recover on the database to remove the existing DB
218 : environment (since changes only take effect on new
219 : environments). */
220 0 : env->set_lk_max_locks(10000);
221 0 : env->set_lk_max_lockers(10000);
222 0 : env->set_lk_max_objects(10000);
223 0 : env->set_lk_detect(DB_LOCK_DEFAULT);
224 :
225 : /* Dangerous, probably, but from the docs it *seems* that BDB
226 : shouldn't sync when DB_TXN_WRITE_NOSYNC is used, but it still
227 : fsync()s sometimes. */
228 0 : db_env_set_func_fsync(my_fsync);
229 :
230 :
231 0 : if (removeOldEnv) {
232 0 : printMsg(lvlError, "removing old Berkeley DB database environment...");
233 0 : env->remove(path.c_str(), DB_FORCE);
234 0 : return;
235 : }
236 :
237 0 : openEnv(env, path, DB_REGISTER | DB_RECOVER);
238 :
239 0 : deleteEnv.release();
240 0 : this->env = env;
241 : }
242 :
243 :
244 0 : void Database::open(const string & path)
245 : {
246 : try {
247 :
248 0 : open2(path, false);
249 :
250 0 : } catch (DbException e) {
251 :
252 0 : if (e.get_errno() == DB_VERSION_MISMATCH) {
253 : /* Remove the environment while we are holding the global
254 : lock. If things go wrong there, we bail out.
255 : !!! argh, we abolished the global lock :-( */
256 0 : open2(path, true);
257 :
258 : /* Try again. */
259 0 : open2(path, false);
260 :
261 : /* Force a checkpoint, as per the BDB docs. */
262 0 : env->txn_checkpoint(DB_FORCE, 0, 0);
263 :
264 0 : printMsg(lvlError, "database succesfully upgraded to new version");
265 : }
266 :
267 : #if 0
268 : else if (e.get_errno() == DB_RUNRECOVERY) {
269 : /* If recovery is needed, do it. */
270 : printMsg(lvlError, "running recovery...");
271 : open2(path, false, true);
272 : }
273 : #endif
274 :
275 : else
276 0 : rethrow(e);
277 : }
278 0 : }
279 :
280 :
281 0 : void Database::close()
282 : {
283 0 : if (!env) return;
284 :
285 : /* Close the database environment. */
286 0 : debug(format("closing database environment"));
287 :
288 : try {
289 :
290 0 : for (std::map<TableId, Db *>::iterator i = tables.begin();
291 : i != tables.end(); )
292 : {
293 0 : std::map<TableId, Db *>::iterator j = i;
294 0 : ++j;
295 0 : closeTable(i->first);
296 0 : i = j;
297 : }
298 :
299 : /* Do a checkpoint every 128 kilobytes, or every 5 minutes. */
300 0 : env->txn_checkpoint(128, 5, 0);
301 :
302 0 : env->close(0);
303 :
304 0 : } catch (DbException e) { rethrow(e); }
305 :
306 0 : delete env;
307 :
308 0 : env = 0;
309 : }
310 :
311 :
312 0 : TableId Database::openTable(const string & tableName, bool sorted)
313 : {
314 0 : requireEnv();
315 0 : TableId table = nextId++;
316 :
317 : try {
318 :
319 0 : Db * db = new Db(env, 0);
320 :
321 : try {
322 : db->open(0, tableName.c_str(), 0,
323 : sorted ? DB_BTREE : DB_HASH,
324 0 : DB_CREATE | DB_AUTO_COMMIT, 0666);
325 0 : } catch (...) {
326 0 : delete db;
327 0 : throw;
328 : }
329 :
330 0 : tables[table] = db;
331 :
332 0 : } catch (DbException e) { rethrow(e); }
333 :
334 0 : return table;
335 : }
336 :
337 :
338 0 : void Database::closeTable(TableId table)
339 : {
340 : try {
341 0 : Db * db = getDb(table);
342 0 : db->close(DB_NOSYNC);
343 0 : delete db;
344 0 : tables.erase(table);
345 0 : } catch (DbException e) { rethrow(e); }
346 0 : }
347 :
348 :
349 0 : void Database::deleteTable(const string & table)
350 : {
351 : try {
352 0 : env->dbremove(0, table.c_str(), 0, DB_AUTO_COMMIT);
353 0 : } catch (DbException e) { rethrow(e); }
354 0 : }
355 :
356 :
357 : bool Database::queryString(const Transaction & txn, TableId table,
358 0 : const string & key, string & data)
359 : {
360 0 : checkInterrupt();
361 :
362 : try {
363 0 : Db * db = getDb(table);
364 :
365 0 : Dbt kt((void *) key.c_str(), key.length());
366 0 : Dbt dt;
367 :
368 0 : int err = db->get(txn.txn, &kt, &dt, 0);
369 0 : if (err) return false;
370 :
371 0 : if (!dt.get_data())
372 0 : data = "";
373 : else
374 0 : data = string((char *) dt.get_data(), dt.get_size());
375 :
376 0 : } catch (DbException e) { rethrow(e); }
377 :
378 0 : return true;
379 : }
380 :
381 :
382 : bool Database::queryStrings(const Transaction & txn, TableId table,
383 0 : const string & key, Strings & data)
384 : {
385 0 : string d;
386 0 : if (!queryString(txn, table, key, d))
387 0 : return false;
388 0 : data = unpackStrings(d);
389 0 : return true;
390 : }
391 :
392 :
393 : void Database::setString(const Transaction & txn, TableId table,
394 0 : const string & key, const string & data)
395 : {
396 0 : checkInterrupt();
397 : try {
398 0 : Db * db = getDb(table);
399 0 : Dbt kt((void *) key.c_str(), key.length());
400 0 : Dbt dt((void *) data.c_str(), data.length());
401 0 : db->put(txn.txn, &kt, &dt, 0);
402 0 : } catch (DbException e) { rethrow(e); }
403 0 : }
404 :
405 :
406 : void Database::setStrings(const Transaction & txn, TableId table,
407 0 : const string & key, const Strings & data, bool deleteEmpty)
408 : {
409 0 : if (deleteEmpty && data.size() == 0)
410 0 : delPair(txn, table, key);
411 : else
412 0 : setString(txn, table, key, packStrings(data));
413 0 : }
414 :
415 :
416 : void Database::delPair(const Transaction & txn, TableId table,
417 0 : const string & key)
418 : {
419 0 : checkInterrupt();
420 : try {
421 0 : Db * db = getDb(table);
422 0 : Dbt kt((void *) key.c_str(), key.length());
423 0 : db->del(txn.txn, &kt, 0);
424 : /* Non-existence of a pair with the given key is not an
425 : error. */
426 0 : } catch (DbException e) { rethrow(e); }
427 0 : }
428 :
429 :
430 : void Database::enumTable(const Transaction & txn, TableId table,
431 0 : Strings & keys, const string & keyPrefix)
432 : {
433 : try {
434 0 : Db * db = getDb(table);
435 :
436 : Dbc * dbc;
437 0 : db->cursor(txn.txn, &dbc, 0);
438 0 : DestroyDbc destroyDbc(dbc);
439 :
440 0 : Dbt kt, dt;
441 0 : u_int32_t flags = DB_NEXT;
442 :
443 0 : if (!keyPrefix.empty()) {
444 0 : flags = DB_SET_RANGE;
445 0 : kt = Dbt((void *) keyPrefix.c_str(), keyPrefix.size());
446 : }
447 :
448 0 : while (dbc->get(&kt, &dt, flags) != DB_NOTFOUND) {
449 0 : checkInterrupt();
450 0 : string data((char *) kt.get_data(), kt.get_size());
451 0 : if (!keyPrefix.empty() &&
452 : string(data, 0, keyPrefix.size()) != keyPrefix)
453 0 : break;
454 0 : keys.push_back(data);
455 0 : flags = DB_NEXT;
456 0 : }
457 :
458 0 : } catch (DbException e) { rethrow(e); }
459 0 : }
460 :
461 :
462 0 : void Database::clearTable(const Transaction & txn, TableId table)
463 : {
464 : try {
465 0 : Db * db = getDb(table);
466 : u_int32_t count;
467 0 : db->truncate(txn.txn, &count, 0);
468 0 : } catch (DbException e) { rethrow(e); }
469 0 : }
470 :
471 0 :
472 1106 : }
473 553 :
474 : #endif
|