diff options
Diffstat (limited to 'db2/lock/lock_region.c')
-rw-r--r-- | db2/lock/lock_region.c | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/db2/lock/lock_region.c b/db2/lock/lock_region.c new file mode 100644 index 0000000000..b597560744 --- /dev/null +++ b/db2/lock/lock_region.c @@ -0,0 +1,726 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996, 1997, 1998 + * Sleepycat Software. All rights reserved. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)lock_region.c 10.15 (Sleepycat) 6/2/98"; +#endif /* not lint */ + +#ifndef NO_SYSTEM_INCLUDES +#include <sys/types.h> + +#include <ctype.h> +#include <errno.h> +#include <string.h> +#endif + +#include "db_int.h" +#include "shqueue.h" +#include "db_shash.h" +#include "lock.h" +#include "common_ext.h" + +static u_int32_t __lock_count_locks __P((DB_LOCKREGION *)); +static u_int32_t __lock_count_objs __P((DB_LOCKREGION *)); +static void __lock_dump_locker __P((DB_LOCKTAB *, DB_LOCKOBJ *, FILE *)); +static void __lock_dump_object __P((DB_LOCKTAB *, DB_LOCKOBJ *, FILE *)); +static const char *__lock_dump_status __P((db_status_t)); +static void __lock_reset_region __P((DB_LOCKTAB *)); +static int __lock_tabinit __P((DB_ENV *, DB_LOCKREGION *)); + +int +lock_open(path, flags, mode, dbenv, ltp) + const char *path; + u_int32_t flags; + int mode; + DB_ENV *dbenv; + DB_LOCKTAB **ltp; +{ + DB_LOCKTAB *lt; + u_int32_t lock_modes, maxlocks, regflags; + int ret; + + /* Validate arguments. */ +#ifdef HAVE_SPINLOCKS +#define OKFLAGS (DB_CREATE | DB_THREAD) +#else +#define OKFLAGS (DB_CREATE) +#endif + if ((ret = __db_fchk(dbenv, "lock_open", flags, OKFLAGS)) != 0) + return (ret); + + /* Create the lock table structure. */ + if ((lt = (DB_LOCKTAB *)__db_calloc(1, sizeof(DB_LOCKTAB))) == NULL) { + __db_err(dbenv, "%s", strerror(ENOMEM)); + return (ENOMEM); + } + lt->dbenv = dbenv; + + /* Grab the values that we need to compute the region size. */ + lock_modes = DB_LOCK_RW_N; + maxlocks = DB_LOCK_DEFAULT_N; + regflags = REGION_SIZEDEF; + if (dbenv != NULL) { + if (dbenv->lk_modes != 0) { + lock_modes = dbenv->lk_modes; + regflags = 0; + } + if (dbenv->lk_max != 0) { + maxlocks = dbenv->lk_max; + regflags = 0; + } + } + + /* Join/create the lock region. */ + lt->reginfo.dbenv = dbenv; + lt->reginfo.appname = DB_APP_NONE; + if (path == NULL) + lt->reginfo.path = NULL; + else + if ((lt->reginfo.path = (char *)__db_strdup(path)) == NULL) + goto err; + lt->reginfo.file = DB_DEFAULT_LOCK_FILE; + lt->reginfo.mode = mode; + lt->reginfo.size = + LOCK_REGION_SIZE(lock_modes, maxlocks, __db_tablesize(maxlocks)); + lt->reginfo.dbflags = flags; + lt->reginfo.addr = NULL; + lt->reginfo.fd = -1; + lt->reginfo.flags = regflags; + + if ((ret = __db_rattach(<->reginfo)) != 0) + goto err; + + /* Now set up the pointer to the region. */ + lt->region = lt->reginfo.addr; + + /* Initialize the region if we created it. */ + if (F_ISSET(<->reginfo, REGION_CREATED)) { + lt->region->maxlocks = maxlocks; + lt->region->nmodes = lock_modes; + if ((ret = __lock_tabinit(dbenv, lt->region)) != 0) + goto err; + } else { + /* Check for an unexpected region. */ + if (lt->region->magic != DB_LOCKMAGIC) { + __db_err(dbenv, + "lock_open: %s: bad magic number", path); + ret = EINVAL; + goto err; + } + } + + /* Check for automatic deadlock detection. */ + if (dbenv != NULL && dbenv->lk_detect != DB_LOCK_NORUN) { + if (lt->region->detect != DB_LOCK_NORUN && + dbenv->lk_detect != DB_LOCK_DEFAULT && + lt->region->detect != dbenv->lk_detect) { + __db_err(dbenv, + "lock_open: incompatible deadlock detector mode"); + ret = EINVAL; + goto err; + } + if (lt->region->detect == DB_LOCK_NORUN) + lt->region->detect = dbenv->lk_detect; + } + + /* Set up remaining pointers into region. */ + lt->conflicts = (u_int8_t *)lt->region + sizeof(DB_LOCKREGION); + lt->hashtab = + (DB_HASHTAB *)((u_int8_t *)lt->region + lt->region->hash_off); + lt->mem = (void *)((u_int8_t *)lt->region + lt->region->mem_off); + + UNLOCK_LOCKREGION(lt); + *ltp = lt; + return (0); + +err: if (lt->reginfo.addr != NULL) { + UNLOCK_LOCKREGION(lt); + (void)__db_rdetach(<->reginfo); + if (F_ISSET(<->reginfo, REGION_CREATED)) + (void)lock_unlink(path, 1, dbenv); + } + + if (lt->reginfo.path != NULL) + FREES(lt->reginfo.path); + FREE(lt, sizeof(*lt)); + return (ret); +} + +/* + * __lock_tabinit -- + * Initialize the lock region. + */ +static int +__lock_tabinit(dbenv, lrp) + DB_ENV *dbenv; + DB_LOCKREGION *lrp; +{ + struct __db_lock *lp; + struct lock_header *tq_head; + struct obj_header *obj_head; + DB_LOCKOBJ *op; + u_int32_t i, nelements; + const u_int8_t *conflicts; + u_int8_t *curaddr; + + conflicts = dbenv == NULL || dbenv->lk_conflicts == NULL ? + db_rw_conflicts : dbenv->lk_conflicts; + + lrp->table_size = __db_tablesize(lrp->maxlocks); + lrp->magic = DB_LOCKMAGIC; + lrp->version = DB_LOCKVERSION; + lrp->id = 0; + /* + * These fields (lrp->maxlocks, lrp->nmodes) are initialized + * in the caller, since we had to grab those values to size + * the region. + */ + lrp->need_dd = 0; + lrp->detect = DB_LOCK_NORUN; + lrp->numobjs = lrp->maxlocks; + lrp->nlockers = 0; + lrp->mem_bytes = ALIGN(STRING_SIZE(lrp->maxlocks), sizeof(size_t)); + lrp->increment = lrp->hdr.size / 2; + lrp->nconflicts = 0; + lrp->nrequests = 0; + lrp->nreleases = 0; + lrp->ndeadlocks = 0; + + /* + * As we write the region, we've got to maintain the alignment + * for the structures that follow each chunk. This information + * ends up being encapsulated both in here as well as in the + * lock.h file for the XXX_SIZE macros. + */ + /* Initialize conflict matrix. */ + curaddr = (u_int8_t *)lrp + sizeof(DB_LOCKREGION); + memcpy(curaddr, conflicts, lrp->nmodes * lrp->nmodes); + curaddr += lrp->nmodes * lrp->nmodes; + + /* + * Initialize hash table. + */ + curaddr = (u_int8_t *)ALIGNP(curaddr, LOCK_HASH_ALIGN); + lrp->hash_off = curaddr - (u_int8_t *)lrp; + nelements = lrp->table_size; + __db_hashinit(curaddr, nelements); + curaddr += nelements * sizeof(DB_HASHTAB); + + /* + * Initialize locks onto a free list. Since locks contains mutexes, + * we need to make sure that each lock is aligned on a MUTEX_ALIGNMENT + * boundary. + */ + curaddr = (u_int8_t *)ALIGNP(curaddr, MUTEX_ALIGNMENT); + tq_head = &lrp->free_locks; + SH_TAILQ_INIT(tq_head); + + for (i = 0; i++ < lrp->maxlocks; + curaddr += ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)) { + lp = (struct __db_lock *)curaddr; + lp->status = DB_LSTAT_FREE; + SH_TAILQ_INSERT_HEAD(tq_head, lp, links, __db_lock); + } + + /* Initialize objects onto a free list. */ + obj_head = &lrp->free_objs; + SH_TAILQ_INIT(obj_head); + + for (i = 0; i++ < lrp->maxlocks; curaddr += sizeof(DB_LOCKOBJ)) { + op = (DB_LOCKOBJ *)curaddr; + SH_TAILQ_INSERT_HEAD(obj_head, op, links, __db_lockobj); + } + + /* + * Initialize the string space; as for all shared memory allocation + * regions, this requires size_t alignment, since we store the + * lengths of malloc'd areas in the area. + */ + curaddr = (u_int8_t *)ALIGNP(curaddr, sizeof(size_t)); + lrp->mem_off = curaddr - (u_int8_t *)lrp; + __db_shalloc_init(curaddr, lrp->mem_bytes); + return (0); +} + +int +lock_close(lt) + DB_LOCKTAB *lt; +{ + int ret; + + if ((ret = __db_rdetach(<->reginfo)) != 0) + return (ret); + + if (lt->reginfo.path != NULL) + FREES(lt->reginfo.path); + FREE(lt, sizeof(*lt)); + + return (0); +} + +int +lock_unlink(path, force, dbenv) + const char *path; + int force; + DB_ENV *dbenv; +{ + REGINFO reginfo; + int ret; + + memset(®info, 0, sizeof(reginfo)); + reginfo.dbenv = dbenv; + reginfo.appname = DB_APP_NONE; + if (path != NULL && (reginfo.path = (char *)__db_strdup(path)) == NULL) + return (ENOMEM); + reginfo.file = DB_DEFAULT_LOCK_FILE; + ret = __db_runlink(®info, force); + if (reginfo.path != NULL) + FREES(reginfo.path); + return (ret); +} + +/* + * __lock_validate_region -- + * Called at every interface to verify if the region has changed size, + * and if so, to remap the region in and reset the process' pointers. + * + * PUBLIC: int __lock_validate_region __P((DB_LOCKTAB *)); + */ +int +__lock_validate_region(lt) + DB_LOCKTAB *lt; +{ + int ret; + + if (lt->reginfo.size == lt->region->hdr.size) + return (0); + + /* Detach/reattach the region. */ + if ((ret = __db_rreattach(<->reginfo, lt->region->hdr.size)) != 0) + return (ret); + + /* Reset region information. */ + lt->region = lt->reginfo.addr; + __lock_reset_region(lt); + + return (0); +} + +/* + * __lock_grow_region -- + * We have run out of space; time to grow the region. + * + * PUBLIC: int __lock_grow_region __P((DB_LOCKTAB *, int, size_t)); + */ +int +__lock_grow_region(lt, which, howmuch) + DB_LOCKTAB *lt; + int which; + size_t howmuch; +{ + struct __db_lock *newl; + struct lock_header *lock_head; + struct obj_header *obj_head; + DB_LOCKOBJ *op; + DB_LOCKREGION *lrp; + float lock_ratio, obj_ratio; + size_t incr, oldsize, used, usedmem; + u_int32_t i, newlocks, newmem, newobjs, usedlocks, usedobjs; + u_int8_t *curaddr; + int ret; + + lrp = lt->region; + oldsize = lrp->hdr.size; + incr = lrp->increment; + + /* Figure out how much of each sort of space we have. */ + usedmem = lrp->mem_bytes - __db_shalloc_count(lt->mem); + usedobjs = lrp->numobjs - __lock_count_objs(lrp); + usedlocks = lrp->maxlocks - __lock_count_locks(lrp); + + /* + * Figure out what fraction of the used space belongs to each + * different type of "thing" in the region. Then partition the + * new space up according to this ratio. + */ + used = usedmem + + usedlocks * ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT) + + usedobjs * sizeof(DB_LOCKOBJ); + + lock_ratio = usedlocks * + ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT) / (float)used; + obj_ratio = usedobjs * sizeof(DB_LOCKOBJ) / (float)used; + + newlocks = (u_int32_t)(lock_ratio * + incr / ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)); + newobjs = (u_int32_t)(obj_ratio * incr / sizeof(DB_LOCKOBJ)); + newmem = incr - + (newobjs * sizeof(DB_LOCKOBJ) + + newlocks * ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)); + + /* + * Make sure we allocate enough memory for the object being + * requested. + */ + switch (which) { + case DB_LOCK_LOCK: + if (newlocks == 0) { + newlocks = 10; + incr += newlocks * sizeof(struct __db_lock); + } + break; + case DB_LOCK_OBJ: + if (newobjs == 0) { + newobjs = 10; + incr += newobjs * sizeof(DB_LOCKOBJ); + } + break; + case DB_LOCK_MEM: + if (newmem < howmuch * 2) { + incr += howmuch * 2 - newmem; + newmem = howmuch * 2; + } + break; + } + + newmem += ALIGN(incr, sizeof(size_t)) - incr; + incr = ALIGN(incr, sizeof(size_t)); + + /* + * Since we are going to be allocating locks at the beginning of the + * new chunk, we need to make sure that the chunk is MUTEX_ALIGNMENT + * aligned. We did not guarantee this when we created the region, so + * we may need to pad the old region by extra bytes to ensure this + * alignment. + */ + incr += ALIGN(oldsize, MUTEX_ALIGNMENT) - oldsize; + + __db_err(lt->dbenv, + "Growing lock region: %lu locks %lu objs %lu bytes", + (u_long)newlocks, (u_long)newobjs, (u_long)newmem); + + if ((ret = __db_rgrow(<->reginfo, oldsize + incr)) != 0) + return (ret); + lt->region = lt->reginfo.addr; + __lock_reset_region(lt); + + /* Update region parameters. */ + lrp = lt->region; + lrp->increment = incr << 1; + lrp->maxlocks += newlocks; + lrp->numobjs += newobjs; + lrp->mem_bytes += newmem; + + curaddr = (u_int8_t *)lrp + oldsize; + curaddr = (u_int8_t *)ALIGNP(curaddr, MUTEX_ALIGNMENT); + + /* Put new locks onto the free list. */ + lock_head = &lrp->free_locks; + for (i = 0; i++ < newlocks; + curaddr += ALIGN(sizeof(struct __db_lock), MUTEX_ALIGNMENT)) { + newl = (struct __db_lock *)curaddr; + SH_TAILQ_INSERT_HEAD(lock_head, newl, links, __db_lock); + } + + /* Put new objects onto the free list. */ + obj_head = &lrp->free_objs; + for (i = 0; i++ < newobjs; curaddr += sizeof(DB_LOCKOBJ)) { + op = (DB_LOCKOBJ *)curaddr; + SH_TAILQ_INSERT_HEAD(obj_head, op, links, __db_lockobj); + } + + *((size_t *)curaddr) = newmem - sizeof(size_t); + curaddr += sizeof(size_t); + __db_shalloc_free(lt->mem, curaddr); + + return (0); +} + +static void +__lock_reset_region(lt) + DB_LOCKTAB *lt; +{ + lt->conflicts = (u_int8_t *)lt->region + sizeof(DB_LOCKREGION); + lt->hashtab = + (DB_HASHTAB *)((u_int8_t *)lt->region + lt->region->hash_off); + lt->mem = (void *)((u_int8_t *)lt->region + lt->region->mem_off); +} + +/* + * lock_stat -- + * Return LOCK statistics. + */ +int +lock_stat(lt, gspp, db_malloc) + DB_LOCKTAB *lt; + DB_LOCK_STAT **gspp; + void *(*db_malloc) __P((size_t)); +{ + DB_LOCKREGION *rp; + + *gspp = NULL; + + if ((*gspp = db_malloc == NULL ? + (DB_LOCK_STAT *)__db_malloc(sizeof(**gspp)) : + (DB_LOCK_STAT *)db_malloc(sizeof(**gspp))) == NULL) + return (ENOMEM); + + /* Copy out the global statistics. */ + LOCK_LOCKREGION(lt); + + rp = lt->region; + (*gspp)->st_magic = rp->magic; + (*gspp)->st_version = rp->version; + (*gspp)->st_maxlocks = rp->maxlocks; + (*gspp)->st_nmodes = rp->nmodes; + (*gspp)->st_numobjs = rp->numobjs; + (*gspp)->st_nlockers = rp->nlockers; + (*gspp)->st_nconflicts = rp->nconflicts; + (*gspp)->st_nrequests = rp->nrequests; + (*gspp)->st_nreleases = rp->nreleases; + (*gspp)->st_ndeadlocks = rp->ndeadlocks; + (*gspp)->st_region_nowait = rp->hdr.lock.mutex_set_nowait; + (*gspp)->st_region_wait = rp->hdr.lock.mutex_set_wait; + (*gspp)->st_refcnt = rp->hdr.refcnt; + (*gspp)->st_regsize = rp->hdr.size; + + UNLOCK_LOCKREGION(lt); + + return (0); +} + +static u_int32_t +__lock_count_locks(lrp) + DB_LOCKREGION *lrp; +{ + struct __db_lock *newl; + u_int32_t count; + + count = 0; + for (newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock); + newl != NULL; + newl = SH_TAILQ_NEXT(newl, links, __db_lock)) + count++; + + return (count); +} + +static u_int32_t +__lock_count_objs(lrp) + DB_LOCKREGION *lrp; +{ + DB_LOCKOBJ *obj; + u_int32_t count; + + count = 0; + for (obj = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj); + obj != NULL; + obj = SH_TAILQ_NEXT(obj, links, __db_lockobj)) + count++; + + return (count); +} + +#define LOCK_DUMP_CONF 0x001 /* Conflict matrix. */ +#define LOCK_DUMP_FREE 0x002 /* Display lock free list. */ +#define LOCK_DUMP_LOCKERS 0x004 /* Display lockers. */ +#define LOCK_DUMP_MEM 0x008 /* Display region memory. */ +#define LOCK_DUMP_OBJECTS 0x010 /* Display objects. */ +#define LOCK_DUMP_ALL 0x01f /* Display all. */ + +/* + * __lock_dump_region -- + * + * PUBLIC: void __lock_dump_region __P((DB_LOCKTAB *, char *, FILE *)); + */ +void +__lock_dump_region(lt, area, fp) + DB_LOCKTAB *lt; + char *area; + FILE *fp; +{ + struct __db_lock *lp; + DB_LOCKOBJ *op; + DB_LOCKREGION *lrp; + u_int32_t flags, i, j; + int label; + + /* Make it easy to call from the debugger. */ + if (fp == NULL) + fp = stderr; + + for (flags = 0; *area != '\0'; ++area) + switch (*area) { + case 'A': + LF_SET(LOCK_DUMP_ALL); + break; + case 'c': + LF_SET(LOCK_DUMP_CONF); + break; + case 'f': + LF_SET(LOCK_DUMP_FREE); + break; + case 'l': + LF_SET(LOCK_DUMP_LOCKERS); + break; + case 'm': + LF_SET(LOCK_DUMP_MEM); + break; + case 'o': + LF_SET(LOCK_DUMP_OBJECTS); + break; + } + + lrp = lt->region; + + fprintf(fp, "%s\nLock region parameters\n", DB_LINE); + fprintf(fp, "%s: %lu, %s: %lu, %s: %lu, %s: %lu\n%s: %lu, %s: %lu\n", + "table size", (u_long)lrp->table_size, + "hash_off", (u_long)lrp->hash_off, + "increment", (u_long)lrp->increment, + "mem_off", (u_long)lrp->mem_off, + "mem_bytes", (u_long)lrp->mem_bytes, + "need_dd", (u_long)lrp->need_dd); + + if (LF_ISSET(LOCK_DUMP_CONF)) { + fprintf(fp, "\n%s\nConflict matrix\n", DB_LINE); + for (i = 0; i < lrp->nmodes; i++) { + for (j = 0; j < lrp->nmodes; j++) + fprintf(fp, "%lu\t", + (u_long)lt->conflicts[i * lrp->nmodes + j]); + fprintf(fp, "\n"); + } + } + + if (LF_ISSET(LOCK_DUMP_LOCKERS | LOCK_DUMP_OBJECTS)) { + fprintf(fp, "%s\nLock hash buckets\n", DB_LINE); + for (i = 0; i < lrp->table_size; i++) { + label = 1; + for (op = SH_TAILQ_FIRST(<->hashtab[i], __db_lockobj); + op != NULL; + op = SH_TAILQ_NEXT(op, links, __db_lockobj)) { + if (LF_ISSET(LOCK_DUMP_LOCKERS) && + op->type == DB_LOCK_LOCKER) { + if (label) { + fprintf(fp, + "Bucket %lu:\n", (u_long)i); + label = 0; + } + __lock_dump_locker(lt, op, fp); + } + if (LF_ISSET(LOCK_DUMP_OBJECTS) && + op->type == DB_LOCK_OBJTYPE) { + if (label) { + fprintf(fp, + "Bucket %lu:\n", (u_long)i); + label = 0; + } + __lock_dump_object(lt, op, fp); + } + } + } + } + + if (LF_ISSET(LOCK_DUMP_FREE)) { + fprintf(fp, "%s\nLock free list\n", DB_LINE); + for (lp = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock); + lp != NULL; + lp = SH_TAILQ_NEXT(lp, links, __db_lock)) + fprintf(fp, "0x%x: %lu\t%lu\t%s\t0x%x\n", (u_int)lp, + (u_long)lp->holder, (u_long)lp->mode, + __lock_dump_status(lp->status), (u_int)lp->obj); + + fprintf(fp, "%s\nObject free list\n", DB_LINE); + for (op = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj); + op != NULL; + op = SH_TAILQ_NEXT(op, links, __db_lockobj)) + fprintf(fp, "0x%x\n", (u_int)op); + } + + if (LF_ISSET(LOCK_DUMP_MEM)) + __db_shalloc_dump(lt->mem, fp); +} + +static void +__lock_dump_locker(lt, op, fp) + DB_LOCKTAB *lt; + DB_LOCKOBJ *op; + FILE *fp; +{ + struct __db_lock *lp; + u_int32_t locker; + void *ptr; + + ptr = SH_DBT_PTR(&op->lockobj); + memcpy(&locker, ptr, sizeof(u_int32_t)); + fprintf(fp, "L %lx", (u_long)locker); + + lp = SH_LIST_FIRST(&op->heldby, __db_lock); + if (lp == NULL) { + fprintf(fp, "\n"); + return; + } + for (; lp != NULL; lp = SH_LIST_NEXT(lp, locker_links, __db_lock)) + __lock_printlock(lt, lp, 0); +} + +static void +__lock_dump_object(lt, op, fp) + DB_LOCKTAB *lt; + DB_LOCKOBJ *op; + FILE *fp; +{ + struct __db_lock *lp; + u_int32_t j; + u_int8_t *ptr; + u_int ch; + + ptr = SH_DBT_PTR(&op->lockobj); + for (j = 0; j < op->lockobj.size; ptr++, j++) { + ch = *ptr; + fprintf(fp, isprint(ch) ? "%c" : "\\%o", ch); + } + fprintf(fp, "\n"); + + fprintf(fp, "H:"); + for (lp = + SH_TAILQ_FIRST(&op->holders, __db_lock); + lp != NULL; + lp = SH_TAILQ_NEXT(lp, links, __db_lock)) + __lock_printlock(lt, lp, 0); + lp = SH_TAILQ_FIRST(&op->waiters, __db_lock); + if (lp != NULL) { + fprintf(fp, "\nW:"); + for (; lp != NULL; lp = SH_TAILQ_NEXT(lp, links, __db_lock)) + __lock_printlock(lt, lp, 0); + } +} + +static const char * +__lock_dump_status(status) + db_status_t status; +{ + switch (status) { + case DB_LSTAT_ABORTED: + return ("aborted"); + case DB_LSTAT_ERR: + return ("err"); + case DB_LSTAT_FREE: + return ("free"); + case DB_LSTAT_HELD: + return ("held"); + case DB_LSTAT_NOGRANT: + return ("nogrant"); + case DB_LSTAT_PENDING: + return ("pending"); + case DB_LSTAT_WAITING: + return ("waiting"); + } + return ("unknown status"); +} |