]> git.sur5r.net Git - openldap/blobdiff - libraries/libmdb/mdb.c
Fix flag checks in mdb_open
[openldap] / libraries / libmdb / mdb.c
index 15432178c2150678ef28b9d66a6126684ba35d0e..091e8ec569d0a0512bb5ca5fb82a889db958ca77 100644 (file)
@@ -32,7 +32,7 @@
  * 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
+#define _GNU_SOURCE 1
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/param.h>
 #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)
@@ -825,6 +826,7 @@ struct MDB_txn {
  */
 #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
@@ -913,6 +915,10 @@ struct MDB_env {
        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 */
@@ -1632,39 +1638,49 @@ mdb_txn_renew0(MDB_txn *txn)
 {
        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 = 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) {
+               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 MDB_READERS_FULL;
+                               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;
-                       /* Save numreaders for un-mutexed mdb_env_close() */
-                       env->me_numreaders = env->me_txns->mti_numreaders;
-                       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);
 
@@ -1804,7 +1820,8 @@ mdb_txn_reset0(MDB_txn *txn)
        MDB_env *env = txn->mt_env;
 
        if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
-               txn->mt_u.reader->mr_txnid = (txnid_t)-1;
+               if (!(env->me_flags & MDB_ROFS))
+                       txn->mt_u.reader->mr_txnid = (txnid_t)-1;
        } else {
                MDB_oldpages *mop;
                MDB_page *dp;
@@ -1933,15 +1950,15 @@ mdb_txn_commit(MDB_txn *txn)
                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];
@@ -1989,7 +2006,7 @@ mdb_txn_commit(MDB_txn *txn)
                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",
@@ -2124,7 +2141,7 @@ again:
        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 */
@@ -2401,7 +2418,7 @@ static int
 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;
@@ -2417,9 +2434,12 @@ mdb_env_write_meta(MDB_txn *txn)
                toggle, txn->mt_dbs[MAIN_DBI].md_root);
 
        env = txn->mt_env;
+       mp = env->me_metas[toggle];
 
        if (env->me_flags & MDB_WRITEMAP) {
-               MDB_meta *mp = env->me_metas[toggle];
+               /* 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;
@@ -2440,7 +2460,13 @@ mdb_env_write_meta(MDB_txn *txn)
        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;
@@ -2574,14 +2600,13 @@ mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers)
 /** 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)
 {
+       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) {
@@ -2589,11 +2614,11 @@ mdb_env_open2(MDB_env *env, unsigned int flags)
                        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
        {
@@ -2642,7 +2667,6 @@ mdb_env_open2(MDB_env *env, unsigned int flags)
 #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);
@@ -2761,9 +2785,12 @@ mdb_env_share_locks(MDB_env *env, int *excl)
                 * 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);
-               *excl = 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
        {
@@ -2796,7 +2823,9 @@ mdb_env_excl_lock(MDB_env *env, int *excl)
        } else {
                OVERLAPPED ov;
                memset(&ov, 0, sizeof(ov));
-               if (!LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+               if (LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+                       *excl = 0;
+               } else {
                        rc = ErrCode();
                }
        }
@@ -2921,6 +2950,11 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
        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();
+               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
@@ -2933,15 +2967,27 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
 #if !(O_CLOEXEC)
        {
                int fdflags;
-               if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT, mode)) == -1)
+               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)
+       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
@@ -3000,7 +3046,7 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
                        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;
@@ -3128,14 +3174,16 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
                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;
 
-       /* silently ignore WRITEMAP if we're only getting read access */
-       if (F_ISSET(flags, MDB_RDONLY) && F_ISSET(flags, MDB_WRITEMAP))
-               flags ^= MDB_WRITEMAP;
-
 #ifdef _WIN32
        if (F_ISSET(flags, MDB_RDONLY)) {
                oflags = GENERIC_READ;
@@ -3160,7 +3208,7 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
                goto leave;
        }
 
-       if ((rc = mdb_env_open2(env, flags)) == MDB_SUCCESS) {
+       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 {
@@ -3178,7 +3226,9 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
                        }
                }
                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. */
@@ -3215,7 +3265,7 @@ mdb_env_close0(MDB_env *env, int excl)
 {
        int i;
 
-       if (env->me_lfd == INVALID_HANDLE_VALUE) /* 1st field to get inited */
+       if (!(env->me_flags & MDB_ENV_ACTIVE))
                return;
 
        free(env->me_dbflags);
@@ -3276,16 +3326,26 @@ mdb_env_close0(MDB_env *env, int excl)
 #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_lfd = INVALID_HANDLE_VALUE;     /* Mark env as reset */
+       env->me_flags &= ~MDB_ENV_ACTIVE;
 }
 
 int
 mdb_env_copy(MDB_env *env, const char *path)
 {
        MDB_txn *txn = NULL;
-       int rc, len, oflags;
+       int rc, len;
        size_t wsize;
        char *lpath, *ptr;
        HANDLE newfd = INVALID_HANDLE_VALUE;
@@ -3330,13 +3390,25 @@ mdb_env_copy(MDB_env *env, const char *path)
        }
 #endif
 
-       /* Temporarily block writers until we snapshot the meta pages */
-       LOCK_MUTEX_W(env);
-
+       /* 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) {
-               UNLOCK_MUTEX_W(env);
+       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;
@@ -3350,7 +3422,8 @@ mdb_env_copy(MDB_env *env, const char *path)
        rc = write(newfd, env->me_map, wsize);
        rc = (rc == (int)wsize) ? MDB_SUCCESS : ErrCode();
 #endif
-       UNLOCK_MUTEX_W(env);
+       if (! (env->me_flags & MDB_ROFS))
+               UNLOCK_MUTEX_W(env);
 
        if (rc)
                goto leave;
@@ -3358,10 +3431,17 @@ mdb_env_copy(MDB_env *env, const char *path)
        ptr = env->me_map + wsize;
        wsize = txn->mt_next_pgno * env->me_psize - wsize;
 #ifdef _WIN32
-       {
-               DWORD len;
-               rc = WriteFile(newfd, ptr, wsize, &len, NULL);
-               rc = (len == wsize) ? MDB_SUCCESS : ErrCode();
+#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);
@@ -3910,8 +3990,12 @@ mdb_cursor_sibling(MDB_cursor *mc, int move_right)
                       : (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]++;
@@ -3924,7 +4008,7 @@ mdb_cursor_sibling(MDB_cursor *mc, int move_right)
 
        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);
 
@@ -4321,8 +4405,8 @@ mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data)
        }
        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]);
 
@@ -4453,9 +4537,10 @@ fetchm:
        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);
@@ -4503,7 +4588,9 @@ mdb_cursor_touch(MDB_cursor *mc)
 
        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;
@@ -6579,6 +6666,24 @@ mdb_env_stat(MDB_env *env, MDB_stat *arg)
        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
@@ -6602,6 +6707,9 @@ mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi)
                 : ((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;
@@ -6615,11 +6723,20 @@ int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi)
                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;
        }
@@ -6666,7 +6783,7 @@ int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi)
                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;
        }