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