1/*-
2 * Copyright (c) 2004-2009 Oracle.  All rights reserved.
3 *
4 * http://www.apache.org/licenses/LICENSE-2.0.txt
5 *
6 * authors: George Schlossnagle <george@omniti.com>
7 */
8
9extern "C"
10{
11#include "httpd.h"
12#include "http_config.h"
13#include "http_core.h"          /* For REMOTE_NAME */
14#include "http_log.h"
15
16#include "sem_utils.h"
17#include "skiplist.h"
18#include "mm_hash.h"
19}
20
21#include "utils.h"
22#include "db_cxx.h"
23
24/* the semaphore set for the application */
25static int semset;
26
27/* process-local handle for global ref count management */
28/* individual semaphores */
29#define OK_TO_PROCEED 0
30#define GLOBAL_LOCK   1
31#define NUM_SEMS      2
32
33/* mm helpers */
34static MM *mm;
35static MM_Hash *ref_counts;
36
37/* locks */
38int env_locks_init()
39{
40    char shmpath[32];
41    unsigned short start[2] = { 1, 1 };
42
43    snprintf(shmpath, 32, "/tmp/.mod_db4.%d", getpid());
44    mm = mm_create(0, shmpath);
45    if(NULL == mm) {
46        return -1;
47    }
48    mm_lock(mm, MM_LOCK_RW);
49    ref_counts = mm_hash_new(mm, NULL);
50    mm_unlock(mm);
51    if (!ref_counts) {
52        return -1;
53    }
54    if((semset = md4_sem_create(NUM_SEMS, start)) < 0) {
55        return -1;
56    }
57    return 0;
58}
59
60void env_global_rw_lock()
61{
62    mm_lock(mm, MM_LOCK_RW);
63}
64
65void env_global_rd_lock()
66{
67    mm_lock(mm, MM_LOCK_RD);
68}
69
70void env_global_unlock()
71{
72    mm_unlock(mm);
73}
74
75void env_wait_for_child_crash()
76{
77    md4_sem_wait_for_zero(semset, OK_TO_PROCEED);
78}
79
80void env_child_crash()
81{
82    md4_sem_set(semset, OK_TO_PROCEED, 0);
83}
84
85void env_ok_to_proceed()
86{
87    md4_sem_set(semset, OK_TO_PROCEED, 1);
88}
89
90/* process resource globals */
91static Skiplist open_transactions;
92static Skiplist open_cursors;
93static Skiplist open_log_cursors;
94static Skiplist open_dbs;
95static Skiplist open_dbenvs;
96
97/* named pointers for db_env and bd skiplists */
98struct named_resource {
99  char *name;
100  void *ptr;
101};
102
103/* skiplist comparitors for void pointers */
104
105static int VP_COMPARE(void *a, void *b)
106{
107    return (a < b) ? (-1) : ((a == b) ? (0) : (1));
108}
109
110/* key for comparing DB *'s in skiplist */
111
112struct db_key {
113    const char *fname;
114    const char *dname;
115};
116
117static int DB_COMPARE(void *a, void *b)
118{
119    int ret;
120    DB *ae = (DB *) a;
121    DB *be = (DB *) b;
122    if(ae->fname == NULL) {
123        if(be->fname == NULL) {
124            return (ae < be) ? (-1) : ((ae == be) ? (0) : (1));
125        }
126        return 1;
127    }
128    else if(be->fname == NULL) {
129        /* ae->fname != NULL, from above */
130        return -1;
131    }
132    ret = strcmp(ae->fname, be->fname);
133    if(ret == 0) {
134        if(ae->dname == NULL) {
135            if(be->dname == NULL) {
136              return 0;
137            }
138            return 1;
139        }
140        else if(be->dname == NULL) {
141            return -1;
142        }
143        ret = strcmp(ae->dname, be->dname);
144    }
145    return ret;
146}
147
148static int DB_COMPARE_K(void *a, void *b)
149{
150    struct db_key *akey = (struct db_key *) a;
151    DB *be = (DB *) b;
152    int ret;
153    if(akey->fname == NULL) {
154        if(be->fname == NULL) {
155            /* should never match here */
156            return (a < b) ? (-1) : ((a == b) ? (0) : (1));
157        }
158        return 1;
159    }
160    else if(be->fname == NULL) {
161        /* akey->fname != NULL, from above */
162        return -1;
163    }
164    ret = strcmp(akey->fname, be->fname);
165    if(ret == 0) {
166        if(akey->dname == NULL) {
167            if(be->dname == NULL) {
168              return 0;
169            }
170            return 1;
171        }
172        else if(be->dname == NULL) {
173            return -1;
174        }
175        ret = strcmp(akey->dname, be->dname);
176    }
177    return ret;
178}
179
180static int DBENV_COMPARE(void *a, void *b)
181{
182    DB_ENV *ae = (DB_ENV *) a;
183    DB_ENV *be = (DB_ENV *) b;
184    return strcmp(ae->db_home, be->db_home);
185}
186
187static int DBENV_COMPARE_K(void *a, void *b)
188{
189    const char *aname = (const char *) a;
190    DB_ENV *be = (DB_ENV *) b;
191    return strcmp(aname, be->db_home);
192}
193
194void env_rsrc_list_init()
195{
196    skiplist_init(&open_transactions);
197    skiplist_set_compare(&open_transactions, VP_COMPARE, VP_COMPARE);
198
199    skiplist_init(&open_cursors);
200    skiplist_set_compare(&open_cursors, VP_COMPARE, VP_COMPARE);
201
202    skiplist_init(&open_log_cursors);
203    skiplist_set_compare(&open_log_cursors, VP_COMPARE, VP_COMPARE);
204
205    skiplist_init(&open_dbs);
206    skiplist_set_compare(&open_dbs, DB_COMPARE, DB_COMPARE_K);
207
208    skiplist_init(&open_dbenvs);
209    skiplist_set_compare(&open_dbenvs, DBENV_COMPARE, DBENV_COMPARE_K);
210}
211
212static void register_cursor(DBC *dbc)
213{
214    skiplist_insert(&open_cursors, dbc);
215}
216
217static void unregister_cursor(DBC *dbc)
218{
219    skiplist_remove(&open_cursors, dbc, NULL);
220}
221
222static void register_log_cursor(DB_LOGC *cursor)
223{
224    skiplist_insert(&open_log_cursors, cursor);
225}
226
227static void unregister_log_cursor(DB_LOGC *cursor)
228{
229    skiplist_remove(&open_log_cursors, cursor, NULL);
230}
231
232static void register_transaction(DB_TXN *txn)
233{
234    skiplist_insert(&open_transactions, txn);
235}
236
237static void unregister_transaction(DB_TXN *txn)
238{
239    skiplist_remove(&open_transactions, txn, NULL);
240}
241
242static DB *retrieve_db(const char *fname, const char *dname)
243{
244    DB *rv;
245    struct db_key key;
246    if(fname == NULL) {
247        return NULL;
248    }
249    key.fname = fname;
250    key.dname = dname;
251    rv = (DB *) skiplist_find(&open_dbs, (void *) &key, NULL);
252    return rv;
253}
254
255static void register_db(DB *db)
256{
257    skiplist_insert(&open_dbs, db);
258}
259
260static void unregister_db(DB *db)
261{
262    struct db_key key;
263    key.fname = db->fname;
264    key.dname = db->dname;
265    skiplist_remove(&open_dbs, &key, NULL);
266}
267
268static DB_ENV *retrieve_db_env(const char *db_home)
269{
270  return (DB_ENV *) skiplist_find(&open_dbenvs, (void *) db_home, NULL);
271}
272
273static void register_db_env(DB_ENV *dbenv)
274{
275    global_ref_count_increase(dbenv->db_home);
276    skiplist_insert(&open_dbenvs, dbenv);
277}
278
279static void unregister_db_env(DB_ENV *dbenv)
280{
281    global_ref_count_decrease(dbenv->db_home);
282    skiplist_remove(&open_dbenvs, dbenv->db_home, NULL);
283}
284
285int global_ref_count_increase(char *path)
286{
287    int refcount = 0;
288    int pathlen = 0;
289    pathlen = strlen(path);
290
291    env_global_rw_lock();
292    refcount = (int) mm_hash_find(ref_counts, path, pathlen);
293    refcount++;
294    mm_hash_update(ref_counts, path, pathlen, (void *)refcount);
295    env_global_unlock();
296    return refcount;
297}
298
299int global_ref_count_decrease(char *path)
300{
301    int refcount = 0;
302    int pathlen = 0;
303    pathlen = strlen(path);
304
305    env_global_rw_lock();
306    refcount = (int) mm_hash_find(ref_counts, path, pathlen);
307    if(refcount > 0) refcount--;
308    mm_hash_update(ref_counts, path, pathlen, (void *)refcount);
309    env_global_unlock();
310    return refcount;
311}
312
313int global_ref_count_get(const char *path)
314{
315    int refcount = 0;
316    int pathlen = 0;
317    pathlen = strlen(path);
318
319    env_global_rd_lock();
320    refcount = (int) mm_hash_find(ref_counts, path, pathlen);
321    env_global_unlock();
322    return refcount;
323}
324
325void global_ref_count_clean()
326{
327    env_global_rd_lock();
328    mm_hash_free(ref_counts);
329    ref_counts = mm_hash_new(mm, NULL);
330    env_global_unlock();
331}
332
333/* wrapper methods  {{{ */
334
335static int (*old_log_cursor_close)(DB_LOGC *, u_int32_t) = NULL;
336static int new_log_cursor_close(DB_LOGC *cursor, u_int32_t flags)
337{
338    unregister_log_cursor(cursor);
339    return old_log_cursor_close(cursor, flags);
340}
341
342static int (*old_db_txn_abort)(DB_TXN *) = NULL;
343static int new_db_txn_abort(DB_TXN *tid)
344{
345    unregister_transaction(tid);
346    return old_db_txn_abort(tid);
347}
348
349static int (*old_db_txn_commit)(DB_TXN *, u_int32_t) = NULL;
350static int new_db_txn_commit(DB_TXN *tid, u_int32_t flags)
351{
352    unregister_transaction(tid);
353    return old_db_txn_commit(tid, flags);
354}
355
356static int (*old_db_txn_discard)(DB_TXN *, u_int32_t) = NULL;
357static int new_db_txn_discard(DB_TXN *tid, u_int32_t flags)
358{
359    unregister_transaction(tid);
360    return old_db_txn_discard(tid, flags);
361}
362
363static int (*old_db_env_txn_begin)(DB_ENV *, DB_TXN *, DB_TXN **, u_int32_t);
364static int new_db_env_txn_begin(DB_ENV *env, DB_TXN *parent, DB_TXN **tid, u_int32_t flags)
365{
366    int ret;
367    if((ret = old_db_env_txn_begin(env, parent, tid, flags)) == 0) {
368        register_transaction(*tid);
369        /* overload DB_TXN->abort */
370        if(old_db_txn_abort == NULL) {
371            old_db_txn_abort = (*tid)->abort;
372        }
373        (*tid)->abort = new_db_txn_abort;
374
375        /* overload DB_TXN->commit */
376        if(old_db_txn_commit == NULL) {
377            old_db_txn_commit = (*tid)->commit;
378        }
379        (*tid)->commit = new_db_txn_commit;
380
381        /* overload DB_TXN->discard */
382        if(old_db_txn_discard == NULL) {
383            old_db_txn_discard = (*tid)->discard;
384        }
385        (*tid)->discard = new_db_txn_discard;
386    }
387    return ret;
388}
389
390static int (*old_db_env_open)(DB_ENV *, const char *, u_int32_t, int) = NULL;
391static int new_db_env_open(DB_ENV *dbenv, const char *db_home, u_int32_t flags, int mode)
392{
393    int ret =666;
394    DB_ENV *cached_dbenv;
395    flags |= DB_INIT_MPOOL;
396    /* if global ref count is 0, open for recovery */
397    if(global_ref_count_get(db_home) == 0) {
398        flags |= DB_RECOVER;
399        flags |= DB_INIT_TXN;
400        flags |= DB_CREATE;
401    }
402    if((cached_dbenv = retrieve_db_env(db_home)) != NULL) {
403        memcpy(dbenv, cached_dbenv, sizeof(DB_ENV));
404        ret = 0;
405    }
406    else if((ret = old_db_env_open(dbenv, db_home, flags, mode)) == 0) {
407        register_db_env(dbenv);
408    }
409    return ret;
410}
411
412static int(*old_db_env_close)(DB_ENV *, u_int32_t) = NULL;
413static int new_db_env_close(DB_ENV *dbenv, u_int32_t flags)
414{
415    int ret;
416    /* we're already locked */
417    unregister_db_env(dbenv);
418    ret = old_db_env_close(dbenv, flags);
419}
420
421static int (*old_db_env_log_cursor)(DB_ENV *, DB_LOGC **, u_int32_t) = NULL;
422static int new_db_env_log_cursor(DB_ENV *dbenv, DB_LOGC **cursop, u_int32_t flags)
423{
424    int ret;
425    if((ret = old_db_env_log_cursor(dbenv, cursop, flags)) == 0) {
426        register_log_cursor(*cursop);
427        if(old_log_cursor_close == NULL) {
428            old_log_cursor_close = (*cursop)->close;
429        }
430        (*cursop)->close = new_log_cursor_close;
431    }
432    return ret;
433}
434
435static int (*old_db_open)(DB *, DB_TXN *, const char *, const char *, DBTYPE, u_int32_t, int) = NULL;
436static int new_db_open(DB *db, DB_TXN *txnid, const char *file,
437                const char *database, DBTYPE type, u_int32_t flags, int mode)
438{
439    int ret;
440    DB *cached_db;
441
442    cached_db = retrieve_db(file, database);
443    if(cached_db) {
444        memcpy(db, cached_db, sizeof(DB));
445        ret = 0;
446    }
447    else if((ret = old_db_open(db, txnid, file, database, type, flags, mode)) == 0) {
448        register_db(db);
449    }
450    return ret;
451}
452
453static int (*old_db_close)(DB *, u_int32_t) = NULL;
454static int new_db_close(DB *db, u_int32_t flags)
455{
456    unregister_db(db);
457    return old_db_close(db, flags);
458}
459
460
461static int (*old_dbc_close)(DBC *);
462static int new_dbc_close(DBC *cursor)
463{
464    unregister_cursor(cursor);
465    return old_dbc_close(cursor);
466}
467
468static int (*old_dbc_dup)(DBC *, DBC **, u_int32_t) = NULL;
469static int new_dbc_dup(DBC *oldcursor, DBC **newcursor, u_int32_t flags)
470{
471    int ret;
472    if((ret = old_dbc_dup(oldcursor, newcursor, flags)) == 0) {
473        register_cursor(*newcursor);
474
475        /* overload DBC->close */
476        (*newcursor)->close = oldcursor->close;
477
478        /* overload DBC->dup */
479        (*newcursor)->dup = oldcursor->dup;
480    }
481    return ret;
482}
483
484static int (*old_db_cursor)(DB *, DB_TXN *, DBC **, u_int32_t) = NULL;
485static int new_db_cursor(DB *db, DB_TXN *txnid, DBC **cursop, u_int32_t flags)
486{
487    int ret;
488    if((ret = old_db_cursor(db, txnid, cursop, flags)) == 0) {
489        register_cursor(*cursop);
490
491        /* overload DBC->close */
492        if(old_dbc_close == NULL) {
493            old_dbc_close = (*cursop)->close;
494        }
495        (*cursop)->close = new_dbc_close;
496
497        /* overload DBC->dup */
498        if(old_dbc_dup == NULL) {
499            old_dbc_dup = (*cursop)->dup;
500        }
501        (*cursop)->dup = new_dbc_dup;
502    }
503    return ret;
504}
505
506/* }}} */
507
508/* {{{ new DB_ENV constructor
509 */
510
511int mod_db4_db_env_create(DB_ENV **dbenvp, u_int32_t flags)
512{
513    int cachesize = 0;
514    int ret;
515    DB_ENV *dbenv;
516
517    if ((ret = db_env_create(dbenvp, 0)) != 0) {
518        /* FIXME report error */
519
520        return ret;
521    }
522    dbenv = *dbenvp;
523    DbEnv::wrap_DB_ENV(dbenv);
524    /* Here we set defaults settings for the db_env */
525    /* grab context info from httpd.conf for error file */
526    /* grab context info for cachesize */
527    if (0 && cachesize) {
528        if ((ret = dbenv->set_cachesize(dbenv, 0, cachesize, 0)) != 0) {
529            dbenv->err(dbenv, ret, "set_cachesize");
530            dbenv->close(dbenv, 0);
531        }
532    }
533    /* overload DB_ENV->open */
534    if(old_db_env_open == NULL) {
535      old_db_env_open = dbenv->open;
536    }
537    dbenv->open = new_db_env_open;
538
539    /* overload DB_ENV->close */
540    if(old_db_env_close == NULL) {
541      old_db_env_close = dbenv->close;
542    }
543    dbenv->close = new_db_env_close;
544
545    /* overload DB_ENV->log_cursor */
546    if(old_db_env_log_cursor == NULL) {
547      old_db_env_log_cursor = dbenv->log_cursor;
548    }
549    dbenv->log_cursor = new_db_env_log_cursor;
550
551    /* overload DB_ENV->txn_begin */
552    if(old_db_env_txn_begin == NULL) {
553      old_db_env_txn_begin = dbenv->txn_begin;
554    }
555    dbenv->txn_begin = new_db_env_txn_begin;
556    return 0;
557}
558/* }}} */
559
560/* {{{ new DB constructor
561 */
562int mod_db4_db_create(DB **dbp, DB_ENV *dbenv, u_int32_t flags)
563{
564    int ret;
565
566flags = 0;
567
568    if((ret = db_create(dbp, dbenv, flags)) == 0) {
569        /* FIXME this should be removed I think register_db(*dbp); */
570        /* overload DB->open */
571        if(old_db_open == NULL) {
572            old_db_open = (*dbp)->open;
573        }
574        (*dbp)->open = new_db_open;
575
576        /* overload DB->close */
577        if(old_db_close == NULL) {
578            old_db_close = (*dbp)->close;
579        }
580        (*dbp)->close = new_db_close;
581
582        /* overload DB->cursor */
583        if(old_db_cursor == NULL) {
584            old_db_cursor = (*dbp)->cursor;
585        }
586        (*dbp)->cursor = new_db_cursor;
587    }
588    return ret;
589}
590
591/* }}} */
592
593void mod_db4_child_clean_request_shutdown()
594{
595    DBC *cursor;
596    DB_TXN *transaction;
597    while(cursor = (DBC *)skiplist_pop(&open_cursors, NULL)) {
598        cursor->close(cursor);
599    }
600    while(transaction = (DB_TXN *)skiplist_pop(&open_transactions, NULL)) {
601        transaction->abort(transaction);
602    }
603}
604
605void mod_db4_child_clean_process_shutdown()
606{
607    DB *db;
608    DB_ENV *dbenv;
609    mod_db4_child_clean_request_shutdown();
610    while(db = (DB *)skiplist_pop(&open_dbs, NULL)) {
611        db->close(db, 0);
612    }
613    while(dbenv = (DB_ENV *)skiplist_pop(&open_dbenvs, NULL)) {
614        DbEnv *dbe = DbEnv::get_DbEnv(dbenv);
615        global_ref_count_decrease(dbenv->db_home);
616        dbe->close(0);
617        delete dbe;
618    }
619}
620/* vim: set ts=4 sts=4 expandtab bs=2 ai fdm=marker: */
621