Ok, considering the interest by Greg and Ryan [from an offline conversation]
in offering refcounted locks, and the fact that exclusive locks hurt with
a large amount of concurency, I'm putting together Greg's and my requirements.
I've documented the shared-lock promotion problem in the apr_sdbm_lock() docs
below.
An interesting solution to the 'expect failure' in an APR_FLOCK_SHARED sdbm
when they try writing, should we return True from apr_sdbm_isreadonly() when
there is a shared lock in place (that can't be upgraded to exclusive)?
Anyway, I'd be happy to commit later today, once greg offers his 2c.
Bill
Index: dbm/sdbm/sdbm.c
===================================================================
RCS file: /home/cvs/apr-util/dbm/sdbm/sdbm.c,v
retrieving revision 1.20
diff -u -r1.20 sdbm.c
--- dbm/sdbm/sdbm.c 2001/05/06 13:21:17 1.20
+++ dbm/sdbm/sdbm.c 2001/05/08 19:46:06
@@ -112,8 +112,13 @@
{
apr_sdbm_t *db = data;
+ /*
+ * Can't rely on apr_sdbm_unlock, since it will merely
+ * decrement the refcnt if several locks are held.
+ */
+ if (db->flags & (SDBM_SHARED_LOCK | SDBM_EXCLUSIVE_LOCK))
+ (void) apr_file_unlock(db->dirf);
(void) apr_file_close(db->dirf);
- (void) sdbm_unlock(db);
(void) apr_file_close(db->pagf);
free(db);
@@ -143,6 +148,17 @@
db->flags |= SDBM_RDONLY;
}
+ /*
+ * adjust the file open flags so that we handle locking
+ * on our own (don't rely on any locking behavior within
+ * an apr_file_t, in case it's ever introduced, and set
+ * our own flag.
+ */
+ if (flags & APR_SHARELOCK) {
+ db->flags |= SDBM_SHARED;
+ flags &= ~APR_SHARELOCK;
+ }
+
flags |= APR_BINARY | APR_READ;
/*
@@ -150,15 +166,17 @@
* If we fail anywhere, undo everything, return NULL.
*/
- if ((status = apr_file_open(&db->pagf, pagname, flags, perms, p))
+ if ((status = apr_file_open(&db->dirf, dirname, flags, perms, p))
!= APR_SUCCESS)
goto error;
- if ((status = sdbm_lock(db, !(db->flags & SDBM_RDONLY)))
+ if ((status = apr_file_open(&db->pagf, pagname, flags, perms, p))
!= APR_SUCCESS)
goto error;
- if ((status = apr_file_open(&db->dirf, dirname, flags, perms, p))
+ if ((status = apr_sdbm_lock(db, (db->flags & SDBM_RDONLY)
+ ? APR_FLOCK_SHARED
+ : APR_FLOCK_EXCLUSIVE))
!= APR_SUCCESS)
goto error;
@@ -173,12 +191,17 @@
* zero size: either a fresh database, or one with a single,
* unsplit data page: dirpage is all zeros.
*/
- db->dirbno = (!finfo.size) ? 0 : -1;
- db->pagbno = -1;
- db->maxbno = finfo.size * BYTESIZ;
+ SDBM_INVALIDATE_CACHE(db, finfo);
/* (apr_pcalloc zeroed the buffers) */
+ /*
+ * if we are opened in SHARED mode, unlock ourself
+ */
+ if (db->flags & SDBM_SHARED)
+ if ((status = apr_sdbm_unlock(db)) != APR_SUCCESS)
+ goto error;
+
/* make sure that we close the database at some point */
apr_pool_cleanup_register(p, db, database_cleanup, apr_pool_cleanup_null);
@@ -187,19 +210,20 @@
return APR_SUCCESS;
error:
+ if (db->dirf && db->pagf)
+ (void) apr_sdbm_unlock(db);
if (db->dirf != NULL)
(void) apr_file_close(db->dirf);
if (db->pagf != NULL) {
- (void) sdbm_unlock(db);
(void) apr_file_close(db->pagf);
}
free(db);
return status;
}
-apr_status_t apr_sdbm_open(apr_sdbm_t **db, const char *file,
- apr_int32_t flags, apr_fileperms_t perms,
- apr_pool_t *p)
+APU_DECLARE(apr_status_t) apr_sdbm_open(apr_sdbm_t **db, const char *file,
+ apr_int32_t flags,
+ apr_fileperms_t perms, apr_pool_t *p)
{
char *dirname = apr_pstrcat(p, file, APR_SDBM_DIRFEXT, NULL);
char *pagname = apr_pstrcat(p, file, APR_SDBM_PAGFEXT, NULL);
@@ -207,24 +231,29 @@
return prep(db, dirname, pagname, flags, perms, p);
}
-apr_status_t apr_sdbm_close(apr_sdbm_t *db)
+APU_DECLARE(apr_status_t) apr_sdbm_close(apr_sdbm_t *db)
{
return apr_pool_cleanup_run(db->pool, db, database_cleanup);
}
-apr_status_t apr_sdbm_fetch(apr_sdbm_t *db, apr_sdbm_datum_t *val,
- apr_sdbm_datum_t key)
+APU_DECLARE(apr_status_t) apr_sdbm_fetch(apr_sdbm_t *db, apr_sdbm_datum_t *val,
+ apr_sdbm_datum_t key)
{
apr_status_t status;
+
if (db == NULL || bad(key))
return APR_EINVAL;
+ if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
+ return status;
+
if ((status = getpage(db, exhash(key))) == APR_SUCCESS) {
*val = getpair(db->pagbuf, key);
/* ### do we want a not-found result? */
- return APR_SUCCESS;
}
+ (void) apr_sdbm_unlock(db);
+
return status;
}
@@ -239,42 +268,43 @@
return status;
}
-apr_status_t apr_sdbm_delete(apr_sdbm_t *db, const apr_sdbm_datum_t key)
+APU_DECLARE(apr_status_t) apr_sdbm_delete(apr_sdbm_t *db,
+ const apr_sdbm_datum_t key)
{
apr_status_t status;
-
+
if (db == NULL || bad(key))
return APR_EINVAL;
if (apr_sdbm_rdonly(db))
return APR_EINVAL;
+
+ if ((status = apr_sdbm_lock(db, APR_FLOCK_EXCLUSIVE)) != APR_SUCCESS)
+ return status;
if ((status = getpage(db, exhash(key))) == APR_SUCCESS) {
if (!delpair(db->pagbuf, key))
/* ### should we define some APRUTIL codes? */
- return APR_EGENERAL;
-
- /*
- * update the page file
- */
- status = write_page(db, db->pagbuf, db->pagbno);
- return status;
+ status = APR_EGENERAL;
+ else
+ status = write_page(db, db->pagbuf, db->pagbno);
}
+
+ (void) apr_sdbm_unlock(db);
- return APR_EACCES;
+ return status;
}
-apr_status_t apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
- apr_sdbm_datum_t val, int flags)
+APU_DECLARE(apr_status_t) apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
+ apr_sdbm_datum_t val, int flags)
{
int need;
register long hash;
apr_status_t status;
-
+
if (db == NULL || bad(key))
return APR_EINVAL;
if (apr_sdbm_rdonly(db))
return APR_EINVAL;
-
need = key.dsize + val.dsize;
/*
* is the pair too big (or too small) for this database ??
@@ -282,6 +312,9 @@
if (need < 0 || need > PAIRMAX)
return APR_EINVAL;
+ if ((status = apr_sdbm_lock(db, APR_FLOCK_EXCLUSIVE)) != APR_SUCCESS)
+ return status;
+
if ((status = getpage(db, (hash = exhash(key)))) == APR_SUCCESS) {
/*
@@ -290,21 +323,28 @@
*/
if (flags == APR_SDBM_REPLACE)
(void) delpair(db->pagbuf, key);
- else if (!(flags & APR_SDBM_INSERTDUP) && duppair(db->pagbuf, key))
- return APR_EEXIST;
+ else if (!(flags & APR_SDBM_INSERTDUP) && duppair(db->pagbuf, key)) {
+ status = APR_EEXIST;
+ goto error;
+ }
/*
* if we do not have enough room, we have to split.
*/
if (!fitpair(db->pagbuf, need))
if ((status = makroom(db, hash, need)) != APR_SUCCESS)
- return status;
+ goto error;
/*
* we have enough room or split is successful. insert the key,
* and update the page file.
*/
(void) putpair(db->pagbuf, key, val);
+
status = write_page(db, db->pagbuf, db->pagbno);
- }
+ }
+
+error:
+ (void) apr_sdbm_unlock(db);
+
return status;
}
@@ -405,6 +445,7 @@
status = APR_SUCCESS;
}
}
+
return status;
}
@@ -412,26 +453,37 @@
* the following two routines will break if
* deletions aren't taken into account. (ndbm bug)
*/
-apr_status_t apr_sdbm_firstkey(apr_sdbm_t *db, apr_sdbm_datum_t *key)
+APU_DECLARE(apr_status_t) apr_sdbm_firstkey(apr_sdbm_t *db,
+ apr_sdbm_datum_t *key)
{
+ apr_status_t status;
+
+ if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
+ return status;
+
/*
* start at page 0
*/
- apr_status_t status;
- if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(0), PBLKSIZ))
- != APR_SUCCESS)
- return status;
+ status = read_from(db->pagf, db->pagbuf, OFF_PAG(0), PBLKSIZ);
- db->pagbno = 0;
- db->blkptr = 0;
- db->keyptr = 0;
+ (void) apr_sdbm_unlock(db);
return getnext(key, db);
}
-apr_status_t apr_sdbm_nextkey(apr_sdbm_t *db, apr_sdbm_datum_t *key)
+APU_DECLARE(apr_status_t) apr_sdbm_nextkey(apr_sdbm_t *db,
+ apr_sdbm_datum_t *key)
{
- return getnext(key, db);
+ apr_status_t status;
+
+ if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
+ return status;
+
+ status = getnext(key, db);
+
+ (void) apr_sdbm_unlock(db);
+
+ return status;
}
/*
@@ -467,9 +519,8 @@
* ### we make it so in read_from anyway.
*/
if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(pagb), PBLKSIZ))
- != APR_SUCCESS) {
+ != APR_SUCCESS)
return status;
- }
if (!chkpage(db->pagbuf))
return APR_ENOSPC; /* ### better error? */
@@ -571,8 +622,11 @@
}
-int apr_sdbm_rdonly(apr_sdbm_t *db)
+APU_DECLARE(int) apr_sdbm_rdonly(apr_sdbm_t *db)
{
+ /* ### Should we return true if the first lock is a share lock,
+ * to reflect that apr_sdbm_store and apr_sdbm_delete will fail?
+ */
return (db->flags & SDBM_RDONLY) != 0;
}
Index: dbm/sdbm/sdbm_lock.c
===================================================================
RCS file: /home/cvs/apr-util/dbm/sdbm/sdbm_lock.c,v
retrieving revision 1.6
diff -u -r1.6 sdbm_lock.c
--- dbm/sdbm/sdbm_lock.c 2001/04/30 17:16:19 1.6
+++ dbm/sdbm/sdbm_lock.c 2001/05/08 19:46:07
@@ -52,25 +52,65 @@
* <http://www.apache.org/>.
*/
+#include "apr_file_info.h"
#include "apr_file_io.h"
#include "apr_sdbm.h"
#include "sdbm_private.h"
+#include "sdbm_tune.h"
/* NOTE: this function blocks until it acquires the lock */
-apr_status_t sdbm_lock(apr_sdbm_t *db, int exclusive)
+APU_DECLARE(apr_status_t) apr_sdbm_lock(apr_sdbm_t *db, int type)
{
- int type;
+ apr_status_t status;
- if (exclusive)
- type = APR_FLOCK_EXCLUSIVE;
- else
- type = APR_FLOCK_SHARED;
+ if (!(type == APR_FLOCK_SHARED || type == APR_FLOCK_EXCLUSIVE))
+ return APR_EINVAL;
- return apr_file_lock(db->pagf, type);
+ if (db->flags & SDBM_EXCLUSIVE_LOCK) {
+ ++db->lckcnt;
+ return APR_SUCCESS;
+ }
+ else if (db->flags & SDBM_SHARED_LOCK) {
+ /*
+ * Cannot promote a shared lock to an exlusive lock
+ * in a cross-platform compatibile manner.
+ */
+ if (type == APR_FLOCK_EXCLUSIVE)
+ return APR_EINVAL;
+ ++db->lckcnt;
+ return APR_SUCCESS;
+ }
+ /*
+ * zero size: either a fresh database, or one with a single,
+ * unsplit data page: dirpage is all zeros.
+ */
+ if ((status = apr_file_lock(db->dirf, type)) == APR_SUCCESS)
+ {
+ apr_finfo_t finfo;
+ if ((status = apr_file_info_get(&finfo, APR_FINFO_SIZE, db->dirf))
+ != APR_SUCCESS) {
+ (void) apr_file_unlock(db->dirf);
+ return status;
+ }
+
+ SDBM_INVALIDATE_CACHE(db, finfo);
+
+ ++db->lckcnt;
+ if (type == APR_FLOCK_SHARED)
+ db->flags |= SDBM_SHARED_LOCK;
+ else if (type == APR_FLOCK_EXCLUSIVE)
+ db->flags |= SDBM_EXCLUSIVE_LOCK;
+ }
+ return status;
}
-apr_status_t sdbm_unlock(apr_sdbm_t *db)
+APU_DECLARE(apr_status_t) apr_sdbm_unlock(apr_sdbm_t *db)
{
- return apr_file_unlock(db->pagf);
+ if (!(db->flags & (SDBM_SHARED_LOCK | SDBM_EXCLUSIVE_LOCK)))
+ return APR_EINVAL;
+ if (--db->lckcnt > 0)
+ return APR_SUCCESS;
+ db->flags &= ~(SDBM_SHARED_LOCK | SDBM_EXCLUSIVE_LOCK);
+ return apr_file_unlock(db->dirf);
}
Index: dbm/sdbm/sdbm_private.h
===================================================================
RCS file: /home/cvs/apr-util/dbm/sdbm/sdbm_private.h,v
retrieving revision 1.6
diff -u -r1.6 sdbm_private.h
--- dbm/sdbm/sdbm_private.h 2001/05/06 12:58:05 1.6
+++ dbm/sdbm/sdbm_private.h 2001/05/08 19:46:07
@@ -79,30 +79,40 @@
#define SPLTMAX 10 /* maximum allowed splits */
/* for apr_sdbm_t.flags */
-#define SDBM_RDONLY 0x1 /* data base open read-only */
+#define SDBM_RDONLY 0x1 /* data base open read-only */
+#define SDBM_SHARED 0x2 /* data base open for sharing */
+#define SDBM_SHARED_LOCK 0x4 /* data base locked for shared read */
+#define SDBM_EXCLUSIVE_LOCK 0x8 /* data base locked for write */
struct apr_sdbm_t {
apr_pool_t *pool;
apr_file_t *dirf; /* directory file descriptor */
apr_file_t *pagf; /* page file descriptor */
- apr_int32_t flags; /* status/error flags, see above */
+ apr_int32_t flags; /* status/error flags, see below */
long maxbno; /* size of dirfile in bits */
long curbit; /* current bit number */
long hmask; /* current hash mask */
long blkptr; /* current block for nextkey */
- int keyptr; /* current key for nextkey */
+ int keyptr; /* current key for nextkey */
long blkno; /* current page to read/write */
long pagbno; /* current page in pagbuf */
char pagbuf[PBLKSIZ]; /* page file block buffer */
long dirbno; /* current block in dirbuf */
char dirbuf[DBLKSIZ]; /* directory file block buffer */
+ int lckcnt; /* number of calls to sdbm_lock */
};
-apr_status_t sdbm_lock(apr_sdbm_t *db, int exclusive);
-apr_status_t sdbm_unlock(apr_sdbm_t *db);
-
extern const apr_sdbm_datum_t sdbm_nullitem;
long sdbm_hash(const char *str, int len);
+
+/*
+ * zero the cache
+ */
+#define SDBM_INVALIDATE_CACHE(db, finfo) \
+ do { db->dirbno = (!finfo.size) ? 0 : -1; \
+ db->pagbno = -1; \
+ db->maxbno = finfo.size * BYTESIZ; \
+ } while (0);
#endif /* SDBM_PRIVATE_H */
Index: include/apr_sdbm.h
===================================================================
RCS file: /home/cvs/apr-util/include/apr_sdbm.h,v
retrieving revision 1.7
diff -u -r1.7 apr_sdbm.h
--- include/apr_sdbm.h 2001/05/06 12:58:05 1.7
+++ include/apr_sdbm.h 2001/05/08 19:46:10
@@ -62,14 +62,24 @@
#ifndef APR_SDBM_H
#define APR_SDBM_H
+#include "apu.h"
#include "apr_errno.h"
#include "apr_file_io.h" /* for apr_fileperms_t */
+/**
+ * @package apr-util SDBM library
+ */
+
+/**
+ * Structure for referencing an sdbm
+ * @defvar apr_sdbm_t
+ */
typedef struct apr_sdbm_t apr_sdbm_t;
-/* utility functions */
-int apr_sdbm_rdonly(apr_sdbm_t *db);
-
+/**
+ * Structure for referencing the datum record within an sdbm
+ * @defvar apr_sdbm_datum_t
+ */
typedef struct {
char *dptr;
int dsize;
@@ -78,25 +88,128 @@
/* The extensions used for the database files */
#define APR_SDBM_DIRFEXT ".dir"
#define APR_SDBM_PAGFEXT ".pag"
-
-/* Standard dbm interface */
-
-apr_status_t apr_sdbm_open(apr_sdbm_t **db, const char *filename,
- apr_int32_t flags, apr_fileperms_t perms,
- apr_pool_t *p);
-apr_status_t apr_sdbm_close(apr_sdbm_t *db);
-
-apr_status_t apr_sdbm_fetch(apr_sdbm_t *db, apr_sdbm_datum_t *val,
apr_sdbm_datum_t key);
-apr_status_t apr_sdbm_delete(apr_sdbm_t *db, const apr_sdbm_datum_t key);
-
-/* * flags to sdbm_store */
+/* flags to sdbm_store */
#define APR_SDBM_INSERT 0
#define APR_SDBM_REPLACE 1
#define APR_SDBM_INSERTDUP 2
-apr_status_t apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
- apr_sdbm_datum_t value, int flags);
-apr_status_t apr_sdbm_firstkey(apr_sdbm_t *db, apr_sdbm_datum_t *key);
-apr_status_t apr_sdbm_nextkey(apr_sdbm_t *db, apr_sdbm_datum_t *key);
+
+/**
+ * Open an sdbm database by file name
+ * @param db The newly opened database
+ * @param name The sdbm file to open
+ * @param mode The flag values (APR_READ and APR_BINARY flags are implicit)
+ * <PRE>
+ * APR_WRITE open for read-write access
+ * APR_CREATE create the sdbm if it does not exist
+ * APR_TRUNCATE empty the contents of the sdbm
+ * APR_EXCL fail for APR_CREATE if the file exists
+ * APR_DELONCLOSE delete the sdbm when closed
+ * APR_SHARELOCK support locking across process/machines
+ * </PRE>
+ * @param perm Permissions to apply to if created
+ * @param pool The pool to use when creating the sdbm
+ * @deffunc apr_status_t apr_sdbm_open(apr_sdbm_t **db, const char *name,
apr_int32_t mode, apr_fileperms_t perms, apr_pool_t *p)
+ * @tip The sdbm name is not a true file name, as sdbm appends suffixes
+ * for seperate data and index files.
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_open(apr_sdbm_t **db, const char *name,
+ apr_int32_t mode,
+ apr_fileperms_t perms, apr_pool_t *p);
+
+/**
+ * Close an sdbm file previously opened by apr_sdbm_open
+ * @param db The database to close
+ * @deffunc apr_status_t apr_sdbm_close(apr_sdbm_t *db)
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_close(apr_sdbm_t *db);
+
+/**
+ * Lock an sdbm database for concurency of multiple operations
+ * @param db The database to lock
+ * @param type The lock type
+ * <PRE>
+ * APR_FLOCK_SHARED
+ * APR_FLOCK_EXCLUSIVE
+ * </PRE>
+ * @deffunc apr_status_t apr_sdbm_lock(apr_sdbm_t *db, int type)
+ * @tip Calls to apr_sdbm_lock may be nested. All apr_sdbm functions
+ * perform implicit locking. Since an APR_FLOCK_SHARED lock cannot be
+ * portably promoted to an APR_FLOCK_EXCLUSIVE lock, apr_sdbm_store and
+ * apr_sdbm_delete calls will fail if an APR_FLOCK_SHARED lock is held.
+ * The apr_sdbm_lock call requires the database to be opened with the
+ * APR_SHARELOCK mode value.
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_lock(apr_sdbm_t *db, int type);
+
+/**
+ * Release an sdbm lock previously aquired by apr_sdbm_lock
+ * @param db The database to unlock
+ * @deffunc apr_status_t apr_sdbm_unlock(apr_sdbm_t *db)
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_unlock(apr_sdbm_t *db);
+
+/**
+ * Fetch an sdbm record value by key
+ * @param db The database
+ * @param value The value datum retrieved for this record
+ * @param key The key datum to find this record
+ * @deffunc apr_status_t apr_status_t apr_sdbm_fetch(apr_sdbm_t *db,
apr_sdbm_datum_t *value, apr_sdbm_datum_t key)
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_fetch(apr_sdbm_t *db,
+ apr_sdbm_datum_t *value,
+ apr_sdbm_datum_t key);
+
+/**
+ * Store an sdbm record value by key
+ * @param db The database
+ * @param key The key datum to store this record by
+ * @param value The value datum to store in this record
+ * @param opt The method used to store the record
+ * <PRE>
+ * APR_SDBM_INSERT return an error if the record exists
+ * APR_SDBM_REPLACE overwrite any existing record for key
+ * </PRE>
+ * @deffunc apr_status_t apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
apr_sdbm_datum_t value, int opt)
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
+ apr_sdbm_datum_t value, int opt);
+
+/**
+ * Delete an sdbm record value by key
+ * @param db The database
+ * @param key The key datum of the record to delete
+ * @deffunc apr_status_t apr_sdbm_delete(apr_sdbm_t *db, const
apr_sdbm_datum_t key)
+ * @tip It is not an error to delete a non-existent record.
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_delete(apr_sdbm_t *db,
+ const apr_sdbm_datum_t key);
+
+/**
+ * Retrieve the first record key from a dbm
+ * @param dbm The database
+ * @param key The key datum of the first record
+ * @deffunc apr_status_t apr_sdbm_nextkey(apr_sdbm_t *db, apr_sdbm_datum_t
*key)
+ * @tip The keys returned are not ordered. To traverse the list of keys
+ * for an sdbm opened with APR_SHARELOCK, the caller must use apr_sdbm_lock
+ * prior to retrieving the first record, and hold the lock until after the
+ * last call to apr_sdbm_nextkey.
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_firstkey(apr_sdbm_t *db, apr_sdbm_datum_t
*key);
+
+/**
+ * Retrieve the next record key from an sdbm
+ * @param db The database
+ * @param key The key datum of the next record
+ * @deffunc apr_status_t apr_sdbm_nextkey(apr_sdbm_t *db, apr_sdbm_datum_t
*key)
+ */
+APU_DECLARE(apr_status_t) apr_sdbm_nextkey(apr_sdbm_t *db, apr_sdbm_datum_t
*key);
+
+/**
+ * Returns true if the sdbm database opened for read-only access
+ * @param db The database to test
+ * @deffunc int apr_sdbm_rdonly(apr_sdbm_t *db)
+ */
+APU_DECLARE(int) apr_sdbm_rdonly(apr_sdbm_t *db);
#endif /* APR_SDBM_H */