* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#endif
#if defined(__APPLE__) || defined (BSD)
-#define USE_POSIX_SEM
+# define MDB_USE_POSIX_SEM 1
+# define MDB_FDATASYNC fsync
+#elif defined(ANDROID)
+# define MDB_FDATASYNC fsync
#endif
#ifndef _WIN32
#include <pthread.h>
-#ifdef USE_POSIX_SEM
+#ifdef MDB_USE_POSIX_SEM
#include <semaphore.h>
#endif
#endif
#define pthread_mutex_t HANDLE
#define pthread_key_t DWORD
#define pthread_self() GetCurrentThreadId()
-#define pthread_key_create(x,y) (*(x) = TlsAlloc())
+#define pthread_key_create(x,y) \
+ ((*(x) = TlsAlloc()) == TLS_OUT_OF_INDEXES ? ErrCode() : 0)
#define pthread_key_delete(x) TlsFree(x)
#define pthread_getspecific(x) TlsGetValue(x)
-#define pthread_setspecific(x,y) TlsSetValue(x,y)
+#define pthread_setspecific(x,y) (TlsSetValue(x,y) ? 0 : ErrCode())
#define pthread_mutex_unlock(x) ReleaseMutex(x)
#define pthread_mutex_lock(x) WaitForSingleObject(x, INFINITE)
#define LOCK_MUTEX_R(env) pthread_mutex_lock((env)->me_rmutex)
#define UNLOCK_MUTEX_W(env) pthread_mutex_unlock((env)->me_wmutex)
#define getpid() GetCurrentProcessId()
#define MDB_FDATASYNC(fd) (!FlushFileBuffers(fd))
+#define MDB_MSYNC(addr,len,flags) (!FlushViewOfFile(addr,len))
#define ErrCode() GetLastError()
#define GET_PAGESIZE(x) {SYSTEM_INFO si; GetSystemInfo(&si); (x) = si.dwPageSize;}
#define close(fd) CloseHandle(fd)
#define munmap(ptr,len) UnmapViewOfFile(ptr)
#else
-#ifdef USE_POSIX_SEM
-#define LOCK_MUTEX_R(env) sem_wait((env)->me_rmutex)
+
+#ifdef MDB_USE_POSIX_SEM
+
+#define LOCK_MUTEX_R(env) mdb_sem_wait((env)->me_rmutex)
#define UNLOCK_MUTEX_R(env) sem_post((env)->me_rmutex)
-#define LOCK_MUTEX_W(env) sem_wait((env)->me_wmutex)
+#define LOCK_MUTEX_W(env) mdb_sem_wait((env)->me_wmutex)
#define UNLOCK_MUTEX_W(env) sem_post((env)->me_wmutex)
-#define MDB_FDATASYNC(fd) fsync(fd)
+
+static int
+mdb_sem_wait(sem_t *sem)
+{
+ int rc;
+ while ((rc = sem_wait(sem)) && (rc = errno) == EINTR) ;
+ return rc;
+}
+
#else
-#ifdef ANDROID
-#define MDB_FDATASYNC(fd) fsync(fd)
-#endif
/** Lock the reader mutex.
*/
#define LOCK_MUTEX_R(env) pthread_mutex_lock(&(env)->me_txns->mti_mutex)
/** Unlock the writer mutex.
*/
#define UNLOCK_MUTEX_W(env) pthread_mutex_unlock(&(env)->me_txns->mti_wmutex)
-#endif /* USE_POSIX_SEM */
+#endif /* MDB_USE_POSIX_SEM */
/** Get the error code for the last failed system function.
*/
#define GET_PAGESIZE(x) ((x) = sysconf(_SC_PAGE_SIZE))
#endif
-#if defined(_WIN32) || defined(USE_POSIX_SEM)
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
#define MNAME_LEN 32
#else
#define MNAME_LEN (sizeof(pthread_mutex_t))
*/
#ifndef MDB_FDATASYNC
# define MDB_FDATASYNC fdatasync
+#endif
+
+#ifndef MDB_MSYNC
+# define MDB_MSYNC(addr,len,flags) msync(addr,len,flags)
+#endif
+
+#ifndef MS_SYNC
+#define MS_SYNC 1
+#endif
+
+#ifndef MS_ASYNC
+#define MS_ASYNC 0
#endif
/** A page number in the database.
fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, __VA_ARGS__)))
#else
# define DPRINTF(fmt, ...) ((void) 0)
+# define MDB_DEBUG_SKIP
#endif
/** Print a debug string.
* The string is printed literally, with no format processing.
/** An invalid page number.
* Mainly used to denote an empty tree.
*/
-#define P_INVALID (~0UL)
+#define P_INVALID (~(pgno_t)0)
/** Test if a flag \b f is set in a flag word \b w. */
#define F_ISSET(w, f) (((w) & (f)) == (f))
* lock file.
*/
typedef struct MDB_rxbody {
- /** The current Transaction ID when this transaction began.
+ /** Current Transaction ID when this transaction began, or (txnid_t)-1.
* Multiple readers that start at the same time will probably have the
* same ID here. Again, it's not important to exclude them from
* anything; all we need to know is which version of the DB they
uint32_t mtb_magic;
/** Version number of this lock file. Must be set to #MDB_VERSION. */
uint32_t mtb_version;
-#if defined(_WIN32) || defined(USE_POSIX_SEM)
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
char mtb_rmname[MNAME_LEN];
#else
/** Mutex protecting access to this table.
char pad[(sizeof(MDB_txbody)+CACHELINE-1) & ~(CACHELINE-1)];
} mt1;
union {
-#if defined(_WIN32) || defined(USE_POSIX_SEM)
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
char mt2_wmname[MNAME_LEN];
#define mti_wmname mt2.mt2_wmname
#else
#define LEAF2KEY(p, i, ks) ((char *)(p) + PAGEHDRSZ + ((i)*(ks)))
/** Set the \b node's key into \b key, if requested. */
-#define MDB_SET_KEY(node, key) { if ((key) != NULL) { \
+#define MDB_GET_KEY(node, key) { if ((key) != NULL) { \
(key)->mv_size = NODEKSZ(node); (key)->mv_data = NODEKEY(node); } }
/** Information about a single database in the environment. */
*/
#define MDB_TXN_RDONLY 0x01 /**< read-only transaction */
#define MDB_TXN_ERROR 0x02 /**< an error has occurred */
+#define MDB_TXN_DIRTY 0x04 /**< must write, even if dirty list is empty */
/** @} */
unsigned int mt_flags; /**< @ref mdb_txn */
/** Tracks which of the two meta pages was used at the start
HANDLE me_mfd; /**< just for writing the meta pages */
/** Failed to update the meta page. Probably an I/O error. */
#define MDB_FATAL_ERROR 0x80000000U
+ /** Read-only Filesystem. Allow read access, no locking. */
+#define MDB_ROFS 0x40000000U
+ /** Some fields are initialized. */
+#define MDB_ENV_ACTIVE 0x20000000U
uint32_t me_flags; /**< @ref mdb_env */
unsigned int me_psize; /**< size of a page, from #GET_PAGESIZE */
unsigned int me_maxreaders; /**< size of the reader table */
+ unsigned int me_numreaders; /**< max numreaders set by this env */
MDB_dbi me_numdbs; /**< number of DBs opened */
MDB_dbi me_maxdbs; /**< size of the DB table */
+ pid_t me_pid; /**< process ID of this env */
char *me_path; /**< path to the DB files */
char *me_map; /**< the memory map of the data file */
MDB_txninfo *me_txns; /**< the memory map of the lock file */
#ifdef _WIN32
HANDLE me_rmutex; /* Windows mutexes don't reside in shared mem */
HANDLE me_wmutex;
-#endif
-#ifdef USE_POSIX_SEM
- sem_t *me_rmutex; /* Apple doesn't support shared mutexes */
+#elif defined(MDB_USE_POSIX_SEM)
+ sem_t *me_rmutex; /* Shared mutexes are not supported */
sem_t *me_wmutex;
#endif
};
#define MDB_COMMIT_PAGES IOV_MAX
#endif
-static MDB_page *mdb_page_alloc(MDB_cursor *mc, int num);
-static MDB_page *mdb_page_new(MDB_cursor *mc, uint32_t flags, int num);
-static int mdb_page_touch(MDB_cursor *mc);
+static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp);
+static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp);
+static int mdb_page_touch(MDB_cursor *mc);
static int mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **mp);
static int mdb_page_search_root(MDB_cursor *mc,
static int mdb_env_read_header(MDB_env *env, MDB_meta *meta);
static int mdb_env_pick_meta(const MDB_env *env);
static int mdb_env_write_meta(MDB_txn *txn);
+static void mdb_env_close0(MDB_env *env, int excl);
static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp);
static int mdb_node_add(MDB_cursor *mc, indx_t indx,
"MDB_PAGE_NOTFOUND: Requested page not found",
"MDB_CORRUPTED: Located page was wrong type",
"MDB_PANIC: Update of meta page failed",
- "MDB_VERSION_MISMATCH: Database environment version mismatch"
+ "MDB_VERSION_MISMATCH: Database environment version mismatch",
+ "MDB_INVALID: File is not an MDB file",
+ "MDB_MAP_FULL: Environment mapsize limit reached",
+ "MDB_DBS_FULL: Environment maxdbs limit reached",
+ "MDB_READERS_FULL: Environment maxreaders limit reached",
+ "MDB_TLS_FULL: Thread-local storage keys full - too many environments open",
+ "MDB_TXN_FULL: Nested transaction has too many dirty pages - transaction too big",
+ "MDB_CURSOR_FULL: Internal error - cursor stack limit reached",
+ "MDB_PAGE_FULL: Internal error - page has no more space"
};
char *
mdb_strerror(int err)
{
+ int i;
if (!err)
return ("Successful return: 0");
- if (err >= MDB_KEYEXIST && err <= MDB_VERSION_MISMATCH)
- return mdb_errstr[err - MDB_KEYEXIST];
+ if (err >= MDB_KEYEXIST && err <= MDB_LAST_ERRCODE) {
+ i = err - MDB_KEYEXIST;
+ return mdb_errstr[i];
+ }
return strerror(err);
}
/** Display all the keys in the page. */
static void
-mdb_page_keys(MDB_page *mp)
+mdb_page_list(MDB_page *mp)
{
MDB_node *node;
- unsigned int i, nkeys;
+ unsigned int i, nkeys, nsize;
MDB_val key;
DKBUF;
node = NODEPTR(mp, i);
key.mv_size = node->mn_ksize;
key.mv_data = node->mn_data;
- fprintf(stderr, "key %d: %s\n", i, DKEY(&key));
+ nsize = NODESIZE + NODEKSZ(node) + sizeof(indx_t);
+ if (F_ISSET(node->mn_flags, F_BIGDATA))
+ nsize += sizeof(pgno_t);
+ else
+ nsize += NODEDSZ(node);
+ fprintf(stderr, "key %d: nsize %d, %s\n", i, nsize, DKEY(&key));
}
}
mdb_cursor_init(&mc, txn, FREE_DBI, NULL);
while ((rc = mdb_cursor_get(&mc, &key, &data, MDB_NEXT)) == 0)
freecount += *(MDB_ID *)data.mv_data;
- freecount += txn->mt_dbs[0].md_branch_pages + txn->mt_dbs[0].md_leaf_pages +
- txn->mt_dbs[0].md_overflow_pages;
count = 0;
for (i = 0; i<txn->mt_numdbs; i++) {
+ MDB_xcursor mx, *mxp;
+ mxp = (txn->mt_dbs[i].md_flags & MDB_DUPSORT) ? &mx : NULL;
+ mdb_cursor_init(&mc, txn, i, mxp);
+ if (txn->mt_dbs[i].md_root == P_INVALID)
+ continue;
count += txn->mt_dbs[i].md_branch_pages +
txn->mt_dbs[i].md_leaf_pages +
txn->mt_dbs[i].md_overflow_pages;
if (txn->mt_dbs[i].md_flags & MDB_DUPSORT) {
- MDB_xcursor mx;
- mdb_cursor_init(&mc, txn, i, &mx);
mdb_page_search(&mc, NULL, 0);
do {
unsigned j;
while (mdb_cursor_sibling(&mc, 1) == 0);
}
}
- assert(freecount + count + 2 >= txn->mt_next_pgno - 1);
+ if (freecount + count + 2 /* metapages */ != txn->mt_next_pgno) {
+ fprintf(stderr, "audit: %lu freecount: %lu count: %lu total: %lu next_pgno: %lu\n",
+ txn->mt_txnid, freecount, count+2, freecount+count+2, txn->mt_next_pgno);
+ }
}
#endif
* @param[in] mc cursor A cursor handle identifying the transaction and
* database for which we are allocating.
* @param[in] num the number of pages to allocate.
- * @return Address of the allocated page(s). Requests for multiple pages
+ * @param[out] mp Address of the allocated page(s). Requests for multiple pages
* will always be satisfied by a single contiguous chunk of memory.
+ * @return 0 on success, non-zero on failure.
*/
-static MDB_page *
-mdb_page_alloc(MDB_cursor *mc, int num)
+static int
+mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
{
MDB_txn *txn = mc->mc_txn;
MDB_page *np;
pgno_t pgno = P_INVALID;
MDB_ID2 mid;
+ int rc;
+ *mp = NULL;
/* The free list won't have any content at all until txn 2 has
* committed. The pages freed by txn 2 will be unreferenced
* after txn 3 commits, and so will be safe to re-use in txn 4.
if (!txn->mt_env->me_pghead &&
txn->mt_dbs[FREE_DBI].md_root != P_INVALID) {
/* See if there's anything in the free DB */
+ int j;
+ MDB_reader *r;
MDB_cursor m2;
MDB_node *leaf;
MDB_val data;
- txnid_t *kptr, oldest, last;
+ txnid_t *kptr, last;
mdb_cursor_init(&m2, txn, FREE_DBI, NULL);
if (!txn->mt_env->me_pgfirst) {
last = *kptr;
} else {
MDB_val key;
- int rc, exact;
+ int exact;
again:
exact = 0;
last = txn->mt_env->me_pglast + 1;
last = *(txnid_t *)key.mv_data;
}
- {
- unsigned int i;
- oldest = txn->mt_txnid - 1;
- for (i=0; i<txn->mt_env->me_txns->mti_numreaders; i++) {
- txnid_t mr = txn->mt_env->me_txns->mti_readers[i].mr_txnid;
- if (mr && mr < oldest)
- oldest = mr;
- }
+ /* Unusable if referred by a meta page or reader... */
+ j = 1;
+ if (last < txn->mt_txnid-1) {
+ j = txn->mt_env->me_txns->mti_numreaders;
+ r = txn->mt_env->me_txns->mti_readers + j;
+ for (j = -j; j && (last<r[j].mr_txnid || !r[j].mr_pid); j++) ;
}
- if (oldest > last) {
+ if (!j) {
/* It's usable, grab it.
*/
MDB_oldpages *mop;
*/
if (!idl[0]) goto again;
mop = malloc(sizeof(MDB_oldpages) + MDB_IDL_SIZEOF(idl) - sizeof(pgno_t));
+ if (!mop)
+ return ENOMEM;
mop->mo_next = txn->mt_env->me_pghead;
mop->mo_txnid = last;
txn->mt_env->me_pghead = mop;
/* DB size is maxed out */
if (txn->mt_next_pgno + num >= txn->mt_env->me_maxpg) {
DPUTS("DB size maxed out");
- return NULL;
+ return MDB_MAP_FULL;
}
}
- if (txn->mt_env->me_dpages && num == 1) {
- np = txn->mt_env->me_dpages;
- VGMEMP_ALLOC(txn->mt_env, np, txn->mt_env->me_psize);
- VGMEMP_DEFINED(np, sizeof(np->mp_next));
- txn->mt_env->me_dpages = np->mp_next;
- } else {
- size_t sz = txn->mt_env->me_psize * num;
- if ((np = malloc(sz)) == NULL)
- return NULL;
- VGMEMP_ALLOC(txn->mt_env, np, sz);
- }
- if (pgno == P_INVALID) {
- np->mp_pgno = txn->mt_next_pgno;
- txn->mt_next_pgno += num;
- } else {
+ if (txn->mt_env->me_flags & MDB_WRITEMAP) {
+ if (pgno == P_INVALID) {
+ pgno = txn->mt_next_pgno;
+ txn->mt_next_pgno += num;
+ }
+ np = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno);
np->mp_pgno = pgno;
+ } else {
+ if (txn->mt_env->me_dpages && num == 1) {
+ np = txn->mt_env->me_dpages;
+ VGMEMP_ALLOC(txn->mt_env, np, txn->mt_env->me_psize);
+ VGMEMP_DEFINED(np, sizeof(np->mp_next));
+ txn->mt_env->me_dpages = np->mp_next;
+ } else {
+ size_t sz = txn->mt_env->me_psize * num;
+ if ((np = malloc(sz)) == NULL)
+ return ENOMEM;
+ VGMEMP_ALLOC(txn->mt_env, np, sz);
+ }
+ if (pgno == P_INVALID) {
+ np->mp_pgno = txn->mt_next_pgno;
+ txn->mt_next_pgno += num;
+ } else {
+ np->mp_pgno = pgno;
+ }
}
mid.mid = np->mp_pgno;
mid.mptr = np;
- mdb_mid2l_insert(txn->mt_u.dirty_list, &mid);
+ if (txn->mt_env->me_flags & MDB_WRITEMAP) {
+ mdb_mid2l_append(txn->mt_u.dirty_list, &mid);
+ } else {
+ mdb_mid2l_insert(txn->mt_u.dirty_list, &mid);
+ }
+ *mp = np;
- return np;
+ return MDB_SUCCESS;
}
/** Copy a page: avoid copying unused portions of the page.
{
MDB_page *mp = mc->mc_pg[mc->mc_top];
pgno_t pgno;
+ int rc;
if (!F_ISSET(mp->mp_flags, P_DIRTY)) {
MDB_page *np;
- if ((np = mdb_page_alloc(mc, 1)) == NULL)
- return ENOMEM;
+ if ((rc = mdb_page_alloc(mc, 1, &np)))
+ return rc;
DPRINTF("touched db %u page %zu -> %zu", mc->mc_dbi, mp->mp_pgno, np->mp_pgno);
assert(mp->mp_pgno != np->mp_pgno);
mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno);
}
/* No - copy it */
np = mdb_page_malloc(mc);
+ if (!np)
+ return ENOMEM;
memcpy(np, mp, mc->mc_txn->mt_env->me_psize);
mid.mid = np->mp_pgno;
mid.mptr = np;
{
int rc = 0;
if (force || !F_ISSET(env->me_flags, MDB_NOSYNC)) {
- if (MDB_FDATASYNC(env->me_fd))
- rc = ErrCode();
+ if (env->me_flags & MDB_WRITEMAP) {
+ int flags = (env->me_flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC;
+ if (MDB_MSYNC(env->me_map, env->me_mapsize, flags))
+ rc = ErrCode();
+#ifdef _WIN32
+ else if (flags == MS_SYNC && MDB_FDATASYNC(env->me_fd))
+ rc = ErrCode();
+#endif
+ } else {
+ if (MDB_FDATASYNC(env->me_fd))
+ rc = ErrCode();
+ }
}
return rc;
}
{
MDB_env *env = txn->mt_env;
unsigned int i;
+ int rc;
/* Setup db info */
txn->mt_numdbs = env->me_numdbs;
txn->mt_dbxs = env->me_dbxs; /* mostly static anyway */
if (txn->mt_flags & MDB_TXN_RDONLY) {
- MDB_reader *r = pthread_getspecific(env->me_txkey);
- if (!r) {
- pid_t pid = getpid();
- pthread_t tid = pthread_self();
-
- LOCK_MUTEX_R(env);
- for (i=0; i<env->me_txns->mti_numreaders; i++)
- if (env->me_txns->mti_readers[i].mr_pid == 0)
- break;
- if (i == env->me_maxreaders) {
+ if (env->me_flags & MDB_ROFS) {
+ i = mdb_env_pick_meta(env);
+ txn->mt_txnid = env->me_metas[i]->mm_txnid;
+ txn->mt_u.reader = NULL;
+ } else {
+ MDB_reader *r = pthread_getspecific(env->me_txkey);
+ if (!r) {
+ pid_t pid = env->me_pid;
+ pthread_t tid = pthread_self();
+
+ LOCK_MUTEX_R(env);
+ for (i=0; i<env->me_txns->mti_numreaders; i++)
+ if (env->me_txns->mti_readers[i].mr_pid == 0)
+ break;
+ if (i == env->me_maxreaders) {
+ UNLOCK_MUTEX_R(env);
+ return MDB_READERS_FULL;
+ }
+ env->me_txns->mti_readers[i].mr_pid = pid;
+ env->me_txns->mti_readers[i].mr_tid = tid;
+ if (i >= env->me_txns->mti_numreaders)
+ env->me_txns->mti_numreaders = i+1;
+ /* Save numreaders for un-mutexed mdb_env_close() */
+ env->me_numreaders = env->me_txns->mti_numreaders;
UNLOCK_MUTEX_R(env);
- return ENOMEM;
+ r = &env->me_txns->mti_readers[i];
+ if ((rc = pthread_setspecific(env->me_txkey, r)) != 0) {
+ env->me_txns->mti_readers[i].mr_pid = 0;
+ return rc;
+ }
}
- env->me_txns->mti_readers[i].mr_pid = pid;
- env->me_txns->mti_readers[i].mr_tid = tid;
- if (i >= env->me_txns->mti_numreaders)
- env->me_txns->mti_numreaders = i+1;
- UNLOCK_MUTEX_R(env);
- r = &env->me_txns->mti_readers[i];
- pthread_setspecific(env->me_txkey, r);
- }
- txn->mt_txnid = r->mr_txnid = env->me_txns->mti_txnid;
+ txn->mt_txnid = r->mr_txnid = env->me_txns->mti_txnid;
+ txn->mt_u.reader = r;
+ }
txn->mt_toggle = txn->mt_txnid & 1;
txn->mt_next_pgno = env->me_metas[txn->mt_toggle]->mm_last_pg+1;
- txn->mt_u.reader = r;
} else {
LOCK_MUTEX_W(env);
{
int rc;
- if (!txn)
+ if (! (txn && txn->mt_flags & MDB_TXN_RDONLY))
return EINVAL;
if (txn->mt_env->me_flags & MDB_FATAL_ERROR) {
DPUTS("environment had fatal error, must shutdown!");
return MDB_PANIC;
}
+ if ((env->me_flags & MDB_RDONLY) && !(flags & MDB_RDONLY))
+ return EACCES;
if (parent) {
- /* parent already has an active child txn */
- if (parent->mt_child) {
+ /* Nested transactions: Max 1 child, write txns only, no writemap */
+ if (parent->mt_child ||
+ (flags & MDB_RDONLY) || (parent->mt_flags & MDB_TXN_RDONLY) ||
+ (env->me_flags & MDB_WRITEMAP))
+ {
return EINVAL;
}
}
MDB_env *env = txn->mt_env;
if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
- txn->mt_u.reader->mr_txnid = 0;
+ if (!(env->me_flags & MDB_ROFS))
+ txn->mt_u.reader->mr_txnid = (txnid_t)-1;
} else {
MDB_oldpages *mop;
MDB_page *dp;
}
}
- /* return all dirty pages to dpage list */
- for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) {
- dp = txn->mt_u.dirty_list[i].mptr;
- if (!IS_OVERFLOW(dp) || dp->mp_pages == 1) {
- dp->mp_next = txn->mt_env->me_dpages;
- VGMEMP_FREE(txn->mt_env, dp);
- txn->mt_env->me_dpages = dp;
- } else {
- /* large pages just get freed directly */
- VGMEMP_FREE(txn->mt_env, dp);
- free(dp);
+ if (!(env->me_flags & MDB_WRITEMAP)) {
+ /* return all dirty pages to dpage list */
+ for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) {
+ dp = txn->mt_u.dirty_list[i].mptr;
+ if (!IS_OVERFLOW(dp) || dp->mp_pages == 1) {
+ dp->mp_next = txn->mt_env->me_dpages;
+ VGMEMP_FREE(txn->mt_env, dp);
+ txn->mt_env->me_dpages = dp;
+ } else {
+ /* large pages just get freed directly */
+ VGMEMP_FREE(txn->mt_env, dp);
+ free(dp);
+ }
}
}
if (txn->mt_parent) {
txn->mt_parent->mt_child = NULL;
- free(txn->mt_free_pgs);
+ mdb_midl_free(txn->mt_free_pgs);
free(txn->mt_u.dirty_list);
return;
} else {
return EINVAL;
}
- /* Merge (and close) our cursors with parent's */
- mdb_cursor_merge(txn);
-
if (txn->mt_parent) {
MDB_db *ip, *jp;
MDB_dbi i;
unsigned x, y;
MDB_ID2L dst, src;
+ /* Merge (and close) our cursors with parent's */
+ mdb_cursor_merge(txn);
+
/* Update parent's DB table */
ip = &txn->mt_parent->mt_dbs[2];
jp = &txn->mt_dbs[2];
for (; y<=src[0].mid; y++) {
if (++x >= MDB_IDL_UM_MAX) {
mdb_txn_abort(txn);
- return ENOMEM;
+ return MDB_TXN_FULL;
}
dst[x] = src[y];
}
return EINVAL;
}
- if (!txn->mt_u.dirty_list[0].mid)
+ if (!txn->mt_u.dirty_list[0].mid && !(txn->mt_flags & MDB_TXN_DIRTY))
goto done;
DPRINTF("committing txn %zu %p on mdbenv %p, root page %zu",
while (env->me_pgfree) {
MDB_oldpages *mop = env->me_pgfree;
env->me_pgfree = mop->mo_next;
- free(mop);;
+ free(mop);
}
/* Check for growth of freelist again */
mdb_audit(txn);
#endif
+ if (env->me_flags & MDB_WRITEMAP) {
+ for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) {
+ dp = txn->mt_u.dirty_list[i].mptr;
+ /* clear dirty flag */
+ dp->mp_flags &= ~P_DIRTY;
+ txn->mt_u.dirty_list[i].mid = 0;
+ }
+ txn->mt_u.dirty_list[0].mid = 0;
+ goto sync;
+ }
+
/* Commit up to MDB_COMMIT_PAGES dirty pages to disk until done.
*/
next = 0;
}
txn->mt_u.dirty_list[0].mid = 0;
+sync:
if ((n = mdb_env_sync(env, 0)) != 0 ||
(n = mdb_env_write_meta(txn)) != MDB_SUCCESS) {
mdb_txn_abort(txn);
else if (rc != MDB_PAGESIZE) {
err = ErrCode();
if (rc > 0)
- err = EINVAL;
+ err = MDB_INVALID;
DPRINTF("read: %s", strerror(err));
return err;
}
if (!F_ISSET(p->mp_flags, P_META)) {
DPRINTF("page %zu not a meta page", p->mp_pgno);
- return EINVAL;
+ return MDB_INVALID;
}
m = METADATA(p);
if (m->mm_magic != MDB_MAGIC) {
DPUTS("meta has invalid magic");
- return EINVAL;
+ return MDB_INVALID;
}
if (m->mm_version != MDB_VERSION) {
mdb_env_write_meta(MDB_txn *txn)
{
MDB_env *env;
- MDB_meta meta, metab;
+ MDB_meta meta, metab, *mp;
off_t off;
int rc, len, toggle;
char *ptr;
toggle, txn->mt_dbs[MAIN_DBI].md_root);
env = txn->mt_env;
-
+ mp = env->me_metas[toggle];
+
+ if (env->me_flags & MDB_WRITEMAP) {
+ /* Persist any increases of mapsize config */
+ if (env->me_mapsize > mp->mm_mapsize)
+ mp->mm_mapsize = env->me_mapsize;
+ mp->mm_dbs[0] = txn->mt_dbs[0];
+ mp->mm_dbs[1] = txn->mt_dbs[1];
+ mp->mm_last_pg = txn->mt_next_pgno - 1;
+ mp->mm_txnid = txn->mt_txnid;
+ if (!(env->me_flags & (MDB_NOMETASYNC|MDB_NOSYNC))) {
+ rc = (env->me_flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC;
+ ptr = env->me_map;
+ if (toggle)
+ ptr += env->me_psize;
+ if (MDB_MSYNC(ptr, env->me_psize, rc)) {
+ rc = ErrCode();
+ goto fail;
+ }
+ }
+ goto done;
+ }
metab.mm_txnid = env->me_metas[toggle]->mm_txnid;
metab.mm_last_pg = env->me_metas[toggle]->mm_last_pg;
ptr = (char *)&meta;
- off = offsetof(MDB_meta, mm_dbs[0].md_depth);
+ if (env->me_mapsize > mp->mm_mapsize) {
+ /* Persist any increases of mapsize config */
+ meta.mm_mapsize = env->me_mapsize;
+ off = offsetof(MDB_meta, mm_mapsize);
+ } else {
+ off = offsetof(MDB_meta, mm_dbs[0].md_depth);
+ }
len = sizeof(MDB_meta) - off;
ptr += off;
#else
r2 = pwrite(env->me_fd, ptr, len, off);
#endif
+fail:
env->me_flags |= MDB_FATAL_ERROR;
return rc;
}
+done:
/* Memory ordering issues are irrelevant; since the entire writer
* is wrapped by wmutex, all of these changes will become visible
* after the wmutex is unlocked. Since the DB is multi-version,
e->me_fd = INVALID_HANDLE_VALUE;
e->me_lfd = INVALID_HANDLE_VALUE;
e->me_mfd = INVALID_HANDLE_VALUE;
+#ifdef MDB_USE_POSIX_SEM
+ e->me_rmutex = SEM_FAILED;
+ e->me_wmutex = SEM_FAILED;
+#endif
+ e->me_pid = getpid();
VGMEMP_CREATE(e,0,0);
*env = e;
return MDB_SUCCESS;
/** Further setup required for opening an MDB environment
*/
static int
-mdb_env_open2(MDB_env *env, unsigned int flags)
+mdb_env_open2(MDB_env *env)
{
- int i, newenv = 0;
+ unsigned int flags = env->me_flags;
+ int i, newenv = 0, prot;
MDB_meta meta;
MDB_page *p;
- env->me_flags = flags;
-
memset(&meta, 0, sizeof(meta));
if ((i = mdb_env_read_header(env, &meta)) != 0) {
return i;
DPUTS("new mdbenv");
newenv = 1;
+ meta.mm_mapsize = env->me_mapsize > DEFAULT_MAPSIZE ? env->me_mapsize : DEFAULT_MAPSIZE;
}
- if (!env->me_mapsize) {
- env->me_mapsize = newenv ? DEFAULT_MAPSIZE : meta.mm_mapsize;
- }
+ if (env->me_mapsize < meta.mm_mapsize)
+ env->me_mapsize = meta.mm_mapsize;
#ifdef _WIN32
{
return ErrCode();
SetFilePointer(env->me_fd, 0, NULL, 0);
}
- mh = CreateFileMapping(env->me_fd, NULL, PAGE_READONLY,
+ mh = CreateFileMapping(env->me_fd, NULL, flags & MDB_WRITEMAP ?
+ PAGE_READWRITE : PAGE_READONLY,
sizehi, sizelo, NULL);
if (!mh)
return ErrCode();
- env->me_map = MapViewOfFileEx(mh, FILE_MAP_READ, 0, 0, env->me_mapsize,
- meta.mm_address);
+ env->me_map = MapViewOfFileEx(mh, flags & MDB_WRITEMAP ?
+ FILE_MAP_WRITE : FILE_MAP_READ,
+ 0, 0, env->me_mapsize, meta.mm_address);
CloseHandle(mh);
if (!env->me_map)
return ErrCode();
i = MAP_SHARED;
if (meta.mm_address && (flags & MDB_FIXEDMAP))
i |= MAP_FIXED;
- env->me_map = mmap(meta.mm_address, env->me_mapsize, PROT_READ, i,
+ prot = PROT_READ;
+ if (flags & MDB_WRITEMAP) {
+ prot |= PROT_WRITE;
+ if (ftruncate(env->me_fd, env->me_mapsize) < 0)
+ return ErrCode();
+ }
+ env->me_map = mmap(meta.mm_address, env->me_mapsize, prot, i,
env->me_fd, 0);
if (env->me_map == MAP_FAILED) {
env->me_map = NULL;
#endif
if (newenv) {
- meta.mm_mapsize = env->me_mapsize;
if (flags & MDB_FIXEDMAP)
meta.mm_address = env->me_map;
i = mdb_env_init_meta(env, &meta);
if (i != MDB_SUCCESS) {
- munmap(env->me_map, env->me_mapsize);
return i;
}
}
{
MDB_reader *reader = ptr;
- reader->mr_txnid = 0;
reader->mr_pid = 0;
- reader->mr_tid = 0;
}
#ifdef _WIN32
#endif
/** Downgrade the exclusive lock on the region back to shared */
-static void
-mdb_env_share_locks(MDB_env *env)
+static int
+mdb_env_share_locks(MDB_env *env, int *excl)
{
- int toggle = mdb_env_pick_meta(env);
+ int rc = 0, toggle = mdb_env_pick_meta(env);
env->me_txns->mti_txnid = env->me_metas[toggle]->mm_txnid;
* then release the existing exclusive lock.
*/
memset(&ov, 0, sizeof(ov));
- LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov);
- UnlockFile(env->me_lfd, 0, 0, 1, 0);
+ if (!LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+ rc = ErrCode();
+ } else {
+ UnlockFile(env->me_lfd, 0, 0, 1, 0);
+ *excl = 0;
+ }
}
#else
{
lock_info.l_whence = SEEK_SET;
lock_info.l_start = 0;
lock_info.l_len = 1;
- fcntl(env->me_lfd, F_SETLK, &lock_info);
+ while ((rc = fcntl(env->me_lfd, F_SETLK, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ *excl = rc ? -1 : 0; /* error may mean we lost the lock */
}
#endif
+
+ return rc;
}
+/** Try to get exlusive lock, otherwise shared.
+ * Maintain *excl = -1: no/unknown lock, 0: shared, 1: exclusive.
+ */
static int
mdb_env_excl_lock(MDB_env *env, int *excl)
{
+ int rc = 0;
#ifdef _WIN32
if (LockFile(env->me_lfd, 0, 0, 1, 0)) {
*excl = 1;
} else {
OVERLAPPED ov;
memset(&ov, 0, sizeof(ov));
- if (!LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
- return ErrCode();
+ if (LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+ *excl = 0;
+ } else {
+ rc = ErrCode();
}
}
#else
lock_info.l_whence = SEEK_SET;
lock_info.l_start = 0;
lock_info.l_len = 1;
- if (!fcntl(env->me_lfd, F_SETLK, &lock_info)) {
+ while ((rc = fcntl(env->me_lfd, F_SETLK, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ if (!rc) {
*excl = 1;
- } else {
+ } else
+# ifdef MDB_USE_POSIX_SEM
+ if (*excl < 0) /* always true when !MDB_USE_POSIX_SEM */
+# endif
+ {
lock_info.l_type = F_RDLCK;
- if (fcntl(env->me_lfd, F_SETLKW, &lock_info)) {
- return ErrCode();
- }
+ while ((rc = fcntl(env->me_lfd, F_SETLKW, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ if (rc == 0)
+ *excl = 0;
}
#endif
- return 0;
+ return rc;
}
-#if defined(_WIN32) || defined(USE_POSIX_SEM)
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
/*
* hash_64 - 64 bit Fowler/Noll/Vo-0 FNV-1a hash code
*
* @param[in] env The MDB environment.
* @param[in] lpath The pathname of the file used for the lock region.
* @param[in] mode The Unix permissions for the file, if we create it.
- * @param[out] excl Set to true if we got an exclusive lock on the region.
+ * @param[out] excl Resulting file lock type: -1 none, 0 shared, 1 exclusive
* @return 0 on success, non-zero on failure.
*/
static int
int rc;
off_t size, rsize;
- *excl = 0;
+ *excl = -1;
#ifdef _WIN32
if ((env->me_lfd = CreateFile(lpath, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) {
rc = ErrCode();
- return rc;
+ if (rc == ERROR_WRITE_PROTECT && (env->me_flags & MDB_RDONLY)) {
+ env->me_flags |= MDB_ROFS;
+ return MDB_SUCCESS;
+ }
+ goto fail_errno;
}
/* Try to get exclusive lock. If we succeed, then
* nobody is using the lock region and we should initialize it.
#if !(O_CLOEXEC)
{
int fdflags;
- if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT, mode)) == -1)
- return ErrCode();
+ if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT, mode)) == -1) {
+ rc = ErrCode();
+ if (rc == EROFS && (env->me_flags & MDB_RDONLY)) {
+ env->me_flags |= MDB_ROFS;
+ return MDB_SUCCESS;
+ }
+ goto fail_errno;
+ }
/* Lose record locks when exec*() */
if ((fdflags = fcntl(env->me_lfd, F_GETFD) | FD_CLOEXEC) >= 0)
fcntl(env->me_lfd, F_SETFD, fdflags);
}
#else /* O_CLOEXEC on Linux: Open file and set FD_CLOEXEC atomically */
- if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT|O_CLOEXEC, mode)) == -1)
- return ErrCode();
+ if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT|O_CLOEXEC, mode)) == -1) {
+ rc = ErrCode();
+ if (rc == EROFS && (env->me_flags & MDB_RDONLY)) {
+ env->me_flags |= MDB_ROFS;
+ return MDB_SUCCESS;
+ }
+ goto fail_errno;
+ }
#endif
/* Try to get exclusive lock. If we succeed, then
size = lseek(env->me_lfd, 0, SEEK_END);
#endif
rsize = (env->me_maxreaders-1) * sizeof(MDB_reader) + sizeof(MDB_txninfo);
- if (size < rsize && *excl) {
+ if (size < rsize && *excl > 0) {
#ifdef _WIN32
SetFilePointer(env->me_lfd, rsize, NULL, 0);
- if (!SetEndOfFile(env->me_lfd)) {
- rc = ErrCode();
- goto fail;
- }
+ if (!SetEndOfFile(env->me_lfd)) goto fail_errno;
#else
- if (ftruncate(env->me_lfd, rsize) != 0) {
- rc = ErrCode();
- goto fail;
- }
+ if (ftruncate(env->me_lfd, rsize) != 0) goto fail_errno;
#endif
} else {
rsize = size;
HANDLE mh;
mh = CreateFileMapping(env->me_lfd, NULL, PAGE_READWRITE,
0, 0, NULL);
- if (!mh) {
- rc = ErrCode();
- goto fail;
- }
+ if (!mh) goto fail_errno;
env->me_txns = MapViewOfFileEx(mh, FILE_MAP_WRITE, 0, 0, rsize, NULL);
CloseHandle(mh);
- if (!env->me_txns) {
- rc = ErrCode();
- goto fail;
- }
+ if (!env->me_txns) goto fail_errno;
#else
void *m = mmap(NULL, rsize, PROT_READ|PROT_WRITE, MAP_SHARED,
env->me_lfd, 0);
- if (m == MAP_FAILED) {
- env->me_txns = NULL;
- rc = ErrCode();
- goto fail;
- }
+ if (m == MAP_FAILED) goto fail_errno;
env->me_txns = m;
#endif
}
- if (*excl) {
+ if (*excl > 0) {
#ifdef _WIN32
BY_HANDLE_FILE_INFORMATION stbuf;
struct {
mdb_all_sa.lpSecurityDescriptor = &mdb_null_sd;
mdb_sec_inited = 1;
}
- GetFileInformationByHandle(env->me_lfd, &stbuf);
+ if (!GetFileInformationByHandle(env->me_lfd, &stbuf)) goto fail_errno;
idbuf.volume = stbuf.dwVolumeSerialNumber;
idbuf.nhigh = stbuf.nFileIndexHigh;
idbuf.nlow = stbuf.nFileIndexLow;
val.mv_size = sizeof(idbuf);
mdb_hash_hex(&val, hexbuf);
sprintf(env->me_txns->mti_rmname, "Global\\MDBr%s", hexbuf);
- env->me_rmutex = CreateMutex(&mdb_all_sa, FALSE, env->me_txns->mti_rmname);
- if (!env->me_rmutex) {
- rc = ErrCode();
- goto fail;
- }
sprintf(env->me_txns->mti_wmname, "Global\\MDBw%s", hexbuf);
+ env->me_rmutex = CreateMutex(&mdb_all_sa, FALSE, env->me_txns->mti_rmname);
+ if (!env->me_rmutex) goto fail_errno;
env->me_wmutex = CreateMutex(&mdb_all_sa, FALSE, env->me_txns->mti_wmname);
- if (!env->me_wmutex) {
- rc = ErrCode();
- goto fail;
- }
-#else /* _WIN32 */
-#ifdef USE_POSIX_SEM
+ if (!env->me_wmutex) goto fail_errno;
+#elif defined(MDB_USE_POSIX_SEM)
struct stat stbuf;
struct {
dev_t dev;
MDB_val val;
char hexbuf[17];
- fstat(env->me_lfd, &stbuf);
+ if (fstat(env->me_lfd, &stbuf)) goto fail_errno;
idbuf.dev = stbuf.st_dev;
idbuf.ino = stbuf.st_ino;
val.mv_data = &idbuf;
val.mv_size = sizeof(idbuf);
mdb_hash_hex(&val, hexbuf);
sprintf(env->me_txns->mti_rmname, "/MDBr%s", hexbuf);
- if (sem_unlink(env->me_txns->mti_rmname)) {
- rc = ErrCode();
- if (rc != ENOENT && rc != EINVAL)
- goto fail;
- }
- env->me_rmutex = sem_open(env->me_txns->mti_rmname, O_CREAT, mode, 1);
- if (!env->me_rmutex) {
- rc = ErrCode();
- goto fail;
- }
sprintf(env->me_txns->mti_wmname, "/MDBw%s", hexbuf);
- if (sem_unlink(env->me_txns->mti_wmname)) {
- rc = ErrCode();
- if (rc != ENOENT && rc != EINVAL)
- goto fail;
- }
- env->me_wmutex = sem_open(env->me_txns->mti_wmname, O_CREAT, mode, 1);
- if (!env->me_wmutex) {
- rc = ErrCode();
- goto fail;
- }
-#else /* USE_POSIX_SEM */
+ /* Clean up after a previous run, if needed: Try to
+ * remove both semaphores before doing anything else.
+ */
+ sem_unlink(env->me_txns->mti_rmname);
+ sem_unlink(env->me_txns->mti_wmname);
+ env->me_rmutex = sem_open(env->me_txns->mti_rmname,
+ O_CREAT|O_EXCL, mode, 1);
+ if (env->me_rmutex == SEM_FAILED) goto fail_errno;
+ env->me_wmutex = sem_open(env->me_txns->mti_wmname,
+ O_CREAT|O_EXCL, mode, 1);
+ if (env->me_wmutex == SEM_FAILED) goto fail_errno;
+#else /* MDB_USE_POSIX_SEM */
pthread_mutexattr_t mattr;
- pthread_mutexattr_init(&mattr);
- rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
- if (rc) {
+ if ((rc = pthread_mutexattr_init(&mattr))
+ || (rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED))
+ || (rc = pthread_mutex_init(&env->me_txns->mti_mutex, &mattr))
+ || (rc = pthread_mutex_init(&env->me_txns->mti_wmutex, &mattr)))
goto fail;
- }
- pthread_mutex_init(&env->me_txns->mti_mutex, &mattr);
- pthread_mutex_init(&env->me_txns->mti_wmutex, &mattr);
-#endif /* USE_POSIX_SEM */
-#endif /* _WIN32 */
+ pthread_mutexattr_destroy(&mattr);
+#endif /* _WIN32 || MDB_USE_POSIX_SEM */
+
env->me_txns->mti_version = MDB_VERSION;
env->me_txns->mti_magic = MDB_MAGIC;
env->me_txns->mti_txnid = 0;
} else {
if (env->me_txns->mti_magic != MDB_MAGIC) {
DPUTS("lock region has invalid magic");
- rc = EINVAL;
+ rc = MDB_INVALID;
goto fail;
}
if (env->me_txns->mti_version != MDB_VERSION) {
}
#ifdef _WIN32
env->me_rmutex = OpenMutex(SYNCHRONIZE, FALSE, env->me_txns->mti_rmname);
- if (!env->me_rmutex) {
- rc = ErrCode();
- goto fail;
- }
+ if (!env->me_rmutex) goto fail_errno;
env->me_wmutex = OpenMutex(SYNCHRONIZE, FALSE, env->me_txns->mti_wmname);
- if (!env->me_wmutex) {
- rc = ErrCode();
- goto fail;
- }
-#endif
-#ifdef USE_POSIX_SEM
+ if (!env->me_wmutex) goto fail_errno;
+#elif defined(MDB_USE_POSIX_SEM)
env->me_rmutex = sem_open(env->me_txns->mti_rmname, 0);
- if (!env->me_rmutex) {
- rc = ErrCode();
- goto fail;
- }
+ if (env->me_rmutex == SEM_FAILED) goto fail_errno;
env->me_wmutex = sem_open(env->me_txns->mti_wmname, 0);
- if (!env->me_wmutex) {
- rc = ErrCode();
- goto fail;
- }
+ if (env->me_wmutex == SEM_FAILED) goto fail_errno;
#endif
}
return MDB_SUCCESS;
+fail_errno:
+ rc = ErrCode();
fail:
- close(env->me_lfd);
- env->me_lfd = INVALID_HANDLE_VALUE;
return rc;
-
}
/** The name of the lock file in the DB environment */
int oflags, rc, len, excl;
char *lpath, *dpath;
+ if (env->me_fd != INVALID_HANDLE_VALUE)
+ return EINVAL;
+
len = strlen(path);
if (flags & MDB_NOSUBDIR) {
rc = len + sizeof(LOCKSUFF) + len + 1;
sprintf(dpath, "%s" DATANAME, path);
}
+ flags |= env->me_flags;
+ /* silently ignore WRITEMAP if we're only getting read access */
+ if (F_ISSET(flags, MDB_RDONLY|MDB_WRITEMAP))
+ flags ^= MDB_WRITEMAP;
+ env->me_flags = flags |= MDB_ENV_ACTIVE;
+
rc = mdb_env_setup_locks(env, lpath, mode, &excl);
if (rc)
goto leave;
goto leave;
}
- if ((rc = mdb_env_open2(env, flags)) == MDB_SUCCESS) {
- if (flags & (MDB_RDONLY|MDB_NOSYNC|MDB_NOMETASYNC)) {
+ if ((rc = mdb_env_open2(env)) == MDB_SUCCESS) {
+ if (flags & (MDB_RDONLY|MDB_NOSYNC|MDB_NOMETASYNC|MDB_WRITEMAP)) {
env->me_mfd = env->me_fd;
} else {
/* synchronous fd for meta writes */
goto leave;
}
}
- env->me_path = strdup(path);
DPRINTF("opened dbenv %p", (void *) env);
- pthread_key_create(&env->me_txkey, mdb_env_reader_dest);
+ rc = pthread_key_create(&env->me_txkey, mdb_env_reader_dest);
+ if (rc)
+ goto leave;
+ env->me_numdbs = 2; /* this notes that me_txkey was set */
#ifdef _WIN32
/* Windows TLS callbacks need help finding their TLS info. */
if (mdb_tls_nkeys < MAX_TLS_KEYS)
mdb_tls_keys[mdb_tls_nkeys++] = env->me_txkey;
else {
- rc = ENOMEM;
+ rc = MDB_TLS_FULL;
goto leave;
}
#endif
- if (excl)
- mdb_env_share_locks(env);
- env->me_numdbs = 2;
+ if (excl > 0) {
+ rc = mdb_env_share_locks(env, &excl);
+ if (rc)
+ goto leave;
+ }
env->me_dbxs = calloc(env->me_maxdbs, sizeof(MDB_dbx));
env->me_dbflags = calloc(env->me_maxdbs, sizeof(uint16_t));
- if (!env->me_dbxs || !env->me_dbflags)
+ env->me_path = strdup(path);
+ if (!env->me_dbxs || !env->me_dbflags || !env->me_path)
rc = ENOMEM;
}
leave:
if (rc) {
- if (env->me_fd != INVALID_HANDLE_VALUE) {
- close(env->me_fd);
- env->me_fd = INVALID_HANDLE_VALUE;
- }
- if (env->me_lfd != INVALID_HANDLE_VALUE) {
- close(env->me_lfd);
- env->me_lfd = INVALID_HANDLE_VALUE;
- }
+ mdb_env_close0(env, excl);
}
free(lpath);
return rc;
}
-void
-mdb_env_close(MDB_env *env)
+/** Destroy resources from mdb_env_open() and clear our readers */
+static void
+mdb_env_close0(MDB_env *env, int excl)
{
- MDB_page *dp;
+ int i;
- if (env == NULL)
+ if (!(env->me_flags & MDB_ENV_ACTIVE))
return;
- VGMEMP_DESTROY(env);
- while (env->me_dpages) {
- dp = env->me_dpages;
- VGMEMP_DEFINED(&dp->mp_next, sizeof(dp->mp_next));
- env->me_dpages = dp->mp_next;
- free(dp);
- }
-
free(env->me_dbflags);
free(env->me_dbxs);
free(env->me_path);
- pthread_key_delete(env->me_txkey);
+ if (env->me_numdbs) {
+ pthread_key_delete(env->me_txkey);
#ifdef _WIN32
- /* Delete our key from the global list */
- { int i;
+ /* Delete our key from the global list */
for (i=0; i<mdb_tls_nkeys; i++)
if (mdb_tls_keys[i] == env->me_txkey) {
mdb_tls_keys[i] = mdb_tls_keys[mdb_tls_nkeys-1];
mdb_tls_nkeys--;
break;
}
- }
#endif
+ }
if (env->me_map) {
munmap(env->me_map, env->me_mapsize);
}
- if (env->me_mfd != env->me_fd)
+ if (env->me_mfd != env->me_fd && env->me_mfd != INVALID_HANDLE_VALUE)
close(env->me_mfd);
- close(env->me_fd);
+ if (env->me_fd != INVALID_HANDLE_VALUE)
+ close(env->me_fd);
if (env->me_txns) {
- pid_t pid = getpid();
- unsigned int i;
- for (i=0; i<env->me_txns->mti_numreaders; i++)
+ pid_t pid = env->me_pid;
+ /* Clearing readers is done in this function because
+ * me_txkey with its destructor must be disabled first.
+ */
+ for (i = env->me_numreaders; --i >= 0; )
if (env->me_txns->mti_readers[i].mr_pid == pid)
env->me_txns->mti_readers[i].mr_pid = 0;
-#ifdef USE_POSIX_SEM
- { int excl = 0;
- if (!mdb_env_excl_lock(env, &excl) && excl) {
- /* we are the only remaining user of the environment.
- clean up semaphores. */
+#ifdef _WIN32
+ if (env->me_rmutex) {
+ CloseHandle(env->me_rmutex);
+ if (env->me_wmutex) CloseHandle(env->me_wmutex);
+ }
+ /* Windows automatically destroys the mutexes when
+ * the last handle closes.
+ */
+#elif defined(MDB_USE_POSIX_SEM)
+ if (env->me_rmutex != SEM_FAILED) {
+ sem_close(env->me_rmutex);
+ if (env->me_wmutex != SEM_FAILED)
+ sem_close(env->me_wmutex);
+ /* If we have the filelock: If we are the
+ * only remaining user, clean up semaphores.
+ */
+ if (excl == 0)
+ mdb_env_excl_lock(env, &excl);
+ if (excl > 0) {
sem_unlink(env->me_txns->mti_rmname);
sem_unlink(env->me_txns->mti_wmname);
}
#endif
munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo));
}
- close(env->me_lfd);
+ if (env->me_lfd != INVALID_HANDLE_VALUE) {
+#ifdef _WIN32
+ if (excl >= 0) {
+ /* Unlock the lockfile. Windows would have unlocked it
+ * after closing anyway, but not necessarily at once.
+ */
+ UnlockFile(env->me_lfd, 0, 0, 1, 0);
+ }
+#endif
+ close(env->me_lfd);
+ }
+
+ env->me_flags &= ~MDB_ENV_ACTIVE;
+}
+
+int
+mdb_env_copy(MDB_env *env, const char *path)
+{
+ MDB_txn *txn = NULL;
+ int rc, len;
+ size_t wsize;
+ char *lpath, *ptr;
+ HANDLE newfd = INVALID_HANDLE_VALUE;
+
+ if (env->me_flags & MDB_NOSUBDIR) {
+ lpath = (char *)path;
+ } else {
+ len = strlen(path);
+ len += sizeof(DATANAME);
+ lpath = malloc(len);
+ if (!lpath)
+ return ENOMEM;
+ sprintf(lpath, "%s" DATANAME, path);
+ }
+
+ /* The destination path must exist, but the destination file must not.
+ * We don't want the OS to cache the writes, since the source data is
+ * already in the OS cache.
+ */
+#ifdef _WIN32
+ newfd = CreateFile(lpath, GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH, NULL);
+#else
+ newfd = open(lpath, O_WRONLY|O_CREAT|O_EXCL
+#ifdef O_DIRECT
+ |O_DIRECT
+#endif
+ , 0666);
+#endif
+ if (!(env->me_flags & MDB_NOSUBDIR))
+ free(lpath);
+ if (newfd == INVALID_HANDLE_VALUE) {
+ rc = ErrCode();
+ goto leave;
+ }
+
+#ifdef F_NOCACHE /* __APPLE__ */
+ rc = fcntl(newfd, F_NOCACHE, 1);
+ if (rc) {
+ rc = ErrCode();
+ goto leave;
+ }
+#endif
+
+ /* Do the lock/unlock of the reader mutex before starting the
+ * write txn. Otherwise other read txns could block writers.
+ */
+ rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
+ if (rc)
+ goto leave;
+
+ if (!(env->me_flags & MDB_ROFS)) {
+ /* We must start the actual read txn after blocking writers */
+ mdb_txn_reset0(txn);
+
+ /* Temporarily block writers until we snapshot the meta pages */
+ LOCK_MUTEX_W(env);
+
+ rc = mdb_txn_renew0(txn);
+ if (rc) {
+ UNLOCK_MUTEX_W(env);
+ goto leave;
+ }
+ }
+
+ wsize = env->me_psize * 2;
+#ifdef _WIN32
+ {
+ DWORD len;
+ rc = WriteFile(newfd, env->me_map, wsize, &len, NULL);
+ rc = (len == wsize) ? MDB_SUCCESS : ErrCode();
+ }
+#else
+ rc = write(newfd, env->me_map, wsize);
+ rc = (rc == (int)wsize) ? MDB_SUCCESS : ErrCode();
+#endif
+ if (! (env->me_flags & MDB_ROFS))
+ UNLOCK_MUTEX_W(env);
+
+ if (rc)
+ goto leave;
+
+ ptr = env->me_map + wsize;
+ wsize = txn->mt_next_pgno * env->me_psize - wsize;
+#ifdef _WIN32
+#define MAX_UINT32 4294967295U
+ while (wsize > 0) {
+ DWORD len, w2;
+ if (wsize > MAX_UINT32)
+ w2 = MAX_UINT32 - env->me_psize + 1; /* write in pagesize chunks */
+ else
+ w2 = wsize;
+ rc = WriteFile(newfd, ptr, w2, &len, NULL);
+ rc = (len == w2) ? MDB_SUCCESS : ErrCode();
+ if (rc) break;
+ wsize -= w2;
+ }
+#else
+ rc = write(newfd, ptr, wsize);
+ rc = (rc == (int)wsize) ? MDB_SUCCESS : ErrCode();
+#endif
+ mdb_txn_abort(txn);
+
+leave:
+ if (newfd != INVALID_HANDLE_VALUE)
+ close(newfd);
+
+ return rc;
+}
+
+void
+mdb_env_close(MDB_env *env)
+{
+ MDB_page *dp;
+
+ if (env == NULL)
+ return;
+
+ VGMEMP_DESTROY(env);
+ while ((dp = env->me_dpages) != NULL) {
+ VGMEMP_DEFINED(&dp->mp_next, sizeof(dp->mp_next));
+ env->me_dpages = dp->mp_next;
+ free(dp);
+ }
+
+ mdb_env_close0(env, 0);
mdb_midl_free(env->me_free_pgs);
free(env);
}
mdb_cursor_pop(MDB_cursor *mc)
{
if (mc->mc_snum) {
-#if MDB_DEBUG
+#ifndef MDB_DEBUG_SKIP
MDB_page *top = mc->mc_pg[mc->mc_top];
#endif
mc->mc_snum--;
if (mc->mc_snum >= CURSOR_STACK) {
assert(mc->mc_snum < CURSOR_STACK);
- return ENOMEM;
+ return MDB_CURSOR_FULL;
}
mc->mc_top = mc->mc_snum++;
{
MDB_page *p = NULL;
+ if (txn->mt_env->me_flags & MDB_WRITEMAP) {
+ if (pgno < txn->mt_next_pgno)
+ p = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno);
+ goto done;
+ }
if (!F_ISSET(txn->mt_flags, MDB_TXN_RDONLY) && txn->mt_u.dirty_list[0].mid) {
unsigned x;
x = mdb_mid2l_search(txn->mt_u.dirty_list, pgno);
if (pgno < txn->mt_next_pgno)
p = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno);
}
+done:
*ret = p;
if (!p) {
DPRINTF("page %zu not found", pgno);
: (mc->mc_ki[mc->mc_top] == 0)) {
DPRINTF("no more keys left, moving to %s sibling",
move_right ? "right" : "left");
- if ((rc = mdb_cursor_sibling(mc, move_right)) != MDB_SUCCESS)
+ if ((rc = mdb_cursor_sibling(mc, move_right)) != MDB_SUCCESS) {
+ /* undo cursor_pop before returning */
+ mc->mc_top++;
+ mc->mc_snum++;
return rc;
+ }
} else {
if (move_right)
mc->mc_ki[mc->mc_top]++;
indx = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(indx), &mp)))
- return rc;;
+ return rc;
mdb_cursor_push(mc, mp);
}
}
- MDB_SET_KEY(leaf, key);
+ MDB_GET_KEY(leaf, key);
return MDB_SUCCESS;
}
}
}
- MDB_SET_KEY(leaf, key);
+ MDB_GET_KEY(leaf, key);
return MDB_SUCCESS;
}
nodekey.mv_data = LEAF2KEY(mp, 0, nodekey.mv_size);
} else {
leaf = NODEPTR(mp, 0);
- MDB_SET_KEY(leaf, &nodekey);
+ MDB_GET_KEY(leaf, &nodekey);
}
rc = mc->mc_dbx->md_cmp(key, &nodekey);
if (rc == 0) {
nkeys-1, nodekey.mv_size);
} else {
leaf = NODEPTR(mp, nkeys-1);
- MDB_SET_KEY(leaf, &nodekey);
+ MDB_GET_KEY(leaf, &nodekey);
}
rc = mc->mc_dbx->md_cmp(key, &nodekey);
if (rc == 0) {
mc->mc_ki[mc->mc_top], nodekey.mv_size);
} else {
leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
- MDB_SET_KEY(leaf, &nodekey);
+ MDB_GET_KEY(leaf, &nodekey);
}
rc = mc->mc_dbx->md_cmp(key, &nodekey);
if (rc == 0) {
}
if (data) {
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
- if (op == MDB_SET || op == MDB_SET_RANGE) {
+ if (op == MDB_SET || op == MDB_SET_KEY || op == MDB_SET_RANGE) {
rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
} else {
int ex2, *ex2p;
}
/* The key already matches in all other cases */
- if (op == MDB_SET_RANGE)
- MDB_SET_KEY(leaf, key);
+ if (op == MDB_SET_RANGE || op == MDB_SET_KEY)
+ MDB_GET_KEY(leaf, key);
DPRINTF("==> cursor placed on key [%s]", DKEY(key));
return rc;
return rc;
}
}
- MDB_SET_KEY(leaf, key);
+ MDB_GET_KEY(leaf, key);
return MDB_SUCCESS;
}
}
assert(IS_LEAF(mc->mc_pg[mc->mc_top]));
- mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1;
mc->mc_flags |= C_INITIALIZED|C_EOF;
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1;
}
leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
}
}
- MDB_SET_KEY(leaf, key);
+ MDB_GET_KEY(leaf, key);
return MDB_SUCCESS;
}
assert(mc);
switch (op) {
+ case MDB_GET_CURRENT:
+ if (!mc->mc_flags & C_INITIALIZED) {
+ rc = EINVAL;
+ } else {
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ if (!NUMKEYS(mp)) {
+ mc->mc_ki[mc->mc_top] = 0;
+ rc = MDB_NOTFOUND;
+ break;
+ }
+ rc = MDB_SUCCESS;
+ if (IS_LEAF2(mp)) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size);
+ } else {
+ MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ MDB_GET_KEY(leaf, key);
+ if (data) {
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ rc = mdb_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_GET_CURRENT);
+ } else {
+ rc = mdb_node_read(mc->mc_txn, leaf, data);
+ }
+ }
+ }
+ }
+ break;
case MDB_GET_BOTH:
case MDB_GET_BOTH_RANGE:
if (data == NULL || mc->mc_xcursor == NULL) {
}
/* FALLTHRU */
case MDB_SET:
+ case MDB_SET_KEY:
case MDB_SET_RANGE:
if (key == NULL || key->mv_size == 0 || key->mv_size > MAXKEYSIZE) {
rc = EINVAL;
case MDB_PREV_NODUP:
if (!(mc->mc_flags & C_INITIALIZED) || (mc->mc_flags & C_EOF)) {
rc = mdb_cursor_last(mc, key, data);
- mc->mc_flags &= ~C_EOF;
- } else
- rc = mdb_cursor_prev(mc, key, data, op);
+ mc->mc_flags |= C_INITIALIZED;
+ mc->mc_ki[mc->mc_top]++;
+ }
+ rc = mdb_cursor_prev(mc, key, data, op);
break;
case MDB_FIRST:
rc = mdb_cursor_first(mc, key, data);
if (mc->mc_dbi > MAIN_DBI && !(*mc->mc_dbflag & DB_DIRTY)) {
MDB_cursor mc2;
- mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL);
+ MDB_xcursor mcx;
+ mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI,
+ mc->mc_txn->mt_dbs[MAIN_DBI].md_flags & MDB_DUPSORT ? &mcx : NULL);
rc = mdb_page_search(&mc2, &mc->mc_dbx->md_name, MDB_PS_MODIFY);
if (rc)
return rc;
MDB_page *np;
/* new database, write a root leaf page */
DPUTS("allocating new root leaf page");
- if ((np = mdb_page_new(mc, P_LEAF, 1)) == NULL) {
- return ENOMEM;
+ if ((rc = mdb_page_new(mc, P_LEAF, 1, &np))) {
+ return rc;
}
mc->mc_snum = 0;
mdb_cursor_push(mc, np);
rdata = &xdata;
xdata.mv_size = sizeof(MDB_db);
xdata.mv_data = &dummy;
- mp = mdb_page_alloc(mc, 1);
- if (!mp)
- return ENOMEM;
+ if ((rc = mdb_page_alloc(mc, 1, &mp)))
+ return rc;
offset = mc->mc_txn->mt_env->me_psize - NODEDSZ(leaf);
flags |= F_DUPDATA|F_SUBDATA;
dummy.md_root = mp->mp_pgno;
* @param[in] flags flags defining what type of page is being allocated.
* @param[in] num the number of pages to allocate. This is usually 1,
* unless allocating overflow pages for a large record.
- * @return Address of a page, or NULL on failure.
+ * @param[out] mp Address of a page, or NULL on failure.
+ * @return 0 on success, non-zero on failure.
*/
-static MDB_page *
-mdb_page_new(MDB_cursor *mc, uint32_t flags, int num)
+static int
+mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp)
{
MDB_page *np;
+ int rc;
- if ((np = mdb_page_alloc(mc, num)) == NULL)
- return NULL;
+ if ((rc = mdb_page_alloc(mc, num, &np)))
+ return rc;
DPRINTF("allocated new mpage %zu, page size %u",
np->mp_pgno, mc->mc_txn->mt_env->me_psize);
np->mp_flags = flags | P_DIRTY;
mc->mc_db->md_overflow_pages += num;
np->mp_pages = num;
}
+ *mp = np;
- return np;
+ return 0;
}
/** Calculate the size of a leaf node.
* @return 0 on success, non-zero on failure. Possible errors are:
* <ul>
* <li>ENOMEM - failed to allocate overflow pages for the node.
- * <li>ENOSPC - there is insufficient room in the page. This error
+ * <li>MDB_PAGE_FULL - there is insufficient room in the page. This error
* should never happen since all callers already calculate the
* page's free space before calling this function.
* </ul>
node_size += sizeof(pgno_t);
} else if (node_size + data->mv_size >= mc->mc_txn->mt_env->me_psize / MDB_MINKEYS) {
int ovpages = OVPAGES(data->mv_size, mc->mc_txn->mt_env->me_psize);
+ int rc;
/* Put data on overflow page. */
DPRINTF("data size is %zu, node would be %zu, put data on overflow page",
data->mv_size, node_size+data->mv_size);
node_size += sizeof(pgno_t);
- if ((ofp = mdb_page_new(mc, P_OVERFLOW, ovpages)) == NULL)
- return ENOMEM;
+ if ((rc = mdb_page_new(mc, P_OVERFLOW, ovpages, &ofp)))
+ return rc;
DPRINTF("allocated overflow page %zu", ofp->mp_pgno);
flags |= F_BIGDATA;
} else {
DPRINTF("upper - lower = %u - %u = %u", mp->mp_upper, mp->mp_lower,
mp->mp_upper - mp->mp_lower);
DPRINTF("node size = %zu", node_size);
- return ENOSPC;
+ return MDB_PAGE_FULL;
}
/* Move higher pointers up one slot. */
return MDB_SUCCESS;
}
+int
+mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc)
+{
+ if (txn == NULL || mc == NULL || mc->mc_dbi >= txn->mt_numdbs)
+ return EINVAL;
+
+ if (txn->mt_cursors)
+ return EINVAL;
+
+ mdb_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor);
+ return MDB_SUCCESS;
+}
+
/* Return the count of duplicate data items for the current key */
int
mdb_cursor_count(MDB_cursor *mc, size_t *countp)
if (delta) {
if (delta > 0 && SIZELEFT(mp) < delta) {
DPRINTF("OUCH! Not enough room, delta = %d", delta);
- return ENOSPC;
+ return MDB_PAGE_FULL;
}
numkeys = NUMKEYS(mp);
DKEY(newkey), mc->mc_ki[mc->mc_top]);
/* Create a right sibling. */
- if ((rp = mdb_page_new(mc, mp->mp_flags, 1)) == NULL)
- return ENOMEM;
+ if ((rc = mdb_page_new(mc, mp->mp_flags, 1, &rp)))
+ return rc;
DPRINTF("new right sibling: page %zu", rp->mp_pgno);
if (mc->mc_snum < 2) {
- if ((pp = mdb_page_new(mc, P_BRANCH, 1)) == NULL)
- return ENOMEM;
+ if ((rc = mdb_page_new(mc, P_BRANCH, 1, &pp)))
+ return rc;
/* shift current top to make room for new parent */
mc->mc_pg[1] = mc->mc_pg[0];
mc->mc_ki[1] = mc->mc_ki[0];
}
nkeys = NUMKEYS(mp);
- split_indx = (nkeys + 1) / 2;
+ split_indx = nkeys / 2;
if (newindx < split_indx)
newpos = 0;
* This check is only needed when the data items are
* relatively large, such that being off by one will
* make the difference between success or failure.
- * When the size of the data items is much smaller than
- * one-half of a page, this check is irrelevant.
+ *
+ * It's also relevant if a page happens to be laid out
+ * such that one half of its nodes are all "small" and
+ * the other half of its nodes are "large." If the new
+ * item is also "large" and falls on the half with
+ * "large" nodes, it also may not fit.
*/
if (IS_LEAF(mp)) {
unsigned int psize, nsize;
/* Maximum free space in an empty page */
pmax = mc->mc_txn->mt_env->me_psize - PAGEHDRSZ;
nsize = mdb_leaf_size(mc->mc_txn->mt_env, newkey, newdata);
- if ((nkeys < 20) || (nsize > pmax/4)) {
+ if ((nkeys < 20) || (nsize > pmax/16)) {
if (newindx <= split_indx) {
psize = nsize;
newpos = 0;
psize += NODEDSZ(node);
psize += psize & 1;
if (psize > pmax) {
- if (i == split_indx - 1 && newindx == split_indx)
- newpos = 1;
+ if (i <= newindx) {
+ split_indx = newindx;
+ if (i < newindx)
+ newpos = 1;
+ }
else
split_indx = i;
break;
psize += NODEDSZ(node);
psize += psize & 1;
if (psize > pmax) {
- split_indx = i+1;
+ if (i >= newindx) {
+ split_indx = newindx;
+ newpos = 0;
+ } else
+ split_indx = i+1;
break;
}
}
* at runtime. Changing other flags requires closing the environment
* and re-opening it with the new flags.
*/
-#define CHANGEABLE (MDB_NOSYNC|MDB_NOMETASYNC)
+#define CHANGEABLE (MDB_NOSYNC|MDB_NOMETASYNC|MDB_MAPASYNC)
int
mdb_env_set_flags(MDB_env *env, unsigned int flag, int onoff)
{
return mdb_stat0(env, &env->me_metas[toggle]->mm_dbs[MAIN_DBI], arg);
}
+int
+mdb_env_info(MDB_env *env, MDB_envinfo *arg)
+{
+ int toggle;
+
+ if (env == NULL || arg == NULL)
+ return EINVAL;
+
+ toggle = mdb_env_pick_meta(env);
+ arg->me_mapaddr = (env->me_flags & MDB_FIXEDMAP) ? env->me_map : 0;
+ arg->me_mapsize = env->me_mapsize;
+ arg->me_maxreaders = env->me_maxreaders;
+ arg->me_numreaders = env->me_numreaders;
+ arg->me_last_pgno = env->me_metas[toggle]->mm_last_pg;
+ arg->me_last_txnid = env->me_metas[toggle]->mm_txnid;
+ return MDB_SUCCESS;
+}
+
/** Set the default comparison functions for a database.
* Called immediately after a database is opened to set the defaults.
* The user can then override them with #mdb_set_compare() or
: ((f & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn));
}
+#define PERSISTENT_FLAGS 0xffff
+#define VALID_FLAGS (MDB_REVERSEKEY|MDB_DUPSORT|MDB_INTEGERKEY|MDB_DUPFIXED|\
+ MDB_INTEGERDUP|MDB_REVERSEDUP|MDB_CREATE)
int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi)
{
MDB_val key, data;
mdb_default_cmp(txn, FREE_DBI);
}
+ if ((flags & VALID_FLAGS) != flags)
+ return EINVAL;
+
/* main DB? */
if (!name) {
*dbi = MAIN_DBI;
- if (flags & (MDB_DUPSORT|MDB_REVERSEKEY|MDB_INTEGERKEY))
- txn->mt_dbs[MAIN_DBI].md_flags |= (flags & (MDB_DUPSORT|MDB_REVERSEKEY|MDB_INTEGERKEY));
+ if (flags & PERSISTENT_FLAGS) {
+ uint16_t f2 = flags & PERSISTENT_FLAGS;
+ /* make sure flag changes get committed */
+ if ((txn->mt_dbs[MAIN_DBI].md_flags | f2) != txn->mt_dbs[MAIN_DBI].md_flags) {
+ txn->mt_dbs[MAIN_DBI].md_flags |= f2;
+ txn->mt_flags |= MDB_TXN_DIRTY;
+ }
+ }
mdb_default_cmp(txn, MAIN_DBI);
return MDB_SUCCESS;
}
}
/* If no free slot and max hit, fail */
- if (!unused && txn->mt_numdbs >= txn->mt_env->me_maxdbs - 1)
- return ENFILE;
+ if (!unused && txn->mt_numdbs >= txn->mt_env->me_maxdbs)
+ return MDB_DBS_FULL;
/* Find the DB info */
dbflag = 0;
data.mv_data = &dummy;
memset(&dummy, 0, sizeof(dummy));
dummy.md_root = P_INVALID;
- dummy.md_flags = flags & 0xffff;
+ dummy.md_flags = flags & PERSISTENT_FLAGS;
rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA);
dbflag = DB_DIRTY;
}
if (!rc)
mdb_close(txn->mt_env, dbi);
} else {
+ /* reset the DB record, mark it dirty */
txn->mt_dbflags[dbi] |= DB_DIRTY;
txn->mt_dbs[dbi].md_depth = 0;
txn->mt_dbs[dbi].md_branch_pages = 0;
txn->mt_dbs[dbi].md_overflow_pages = 0;
txn->mt_dbs[dbi].md_entries = 0;
txn->mt_dbs[dbi].md_root = P_INVALID;
+
+ if (!txn->mt_u.dirty_list[0].mid) {
+ MDB_cursor m2;
+ MDB_val key, data;
+ /* make sure we have at least one dirty page in this txn
+ * otherwise these changes will be ignored.
+ */
+ key.mv_size = sizeof(txnid_t);
+ key.mv_data = &txn->mt_txnid;
+ data.mv_size = sizeof(MDB_ID);
+ data.mv_data = txn->mt_free_pgs;
+ mdb_cursor_init(&m2, txn, FREE_DBI, NULL);
+ rc = mdb_cursor_put(&m2, &key, &data, 0);
+ }
}
leave:
mdb_cursor_close(mc);