root/usr/src/lib/libc/port/gen/nss_common.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Shared code used by the name-service-switch frontends (e.g. getpwnam_r())
 */

#include "lint.h"
#include <mtlib.h>
#include <dlfcn.h>
#include <atomic.h>

#define __NSS_PRIVATE_INTERFACE
#include "nsswitch_priv.h"
#undef  __NSS_PRIVATE_INTERFACE

#include <nss_common.h>
#include <nss_dbdefs.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <thread.h>
#include <synch.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#include "libc.h"
#include "tsd.h"

#include <getxby_door.h>

/*
 * configurable values for default buffer sizes
 */

/*
 * PSARC/2005/133 updated the buffering mechanisms to handle
 * up to 2^64 buffering.  But sets a practical limit of 512*1024.
 * The expectation is the practical limit will be dynamic from
 * nscd.  For now, set the group limit to this value.
 */

#define NSS_BUFLEN_PRACTICAL    (512*1024)

static size_t __nss_buflen_group = NSS_BUFLEN_PRACTICAL;
static size_t __nss_buflen_default = NSS_BUFLEN_DOOR;

/*
 * policy component function interposing definitions:
 * nscd if so desired can interpose it's own switch functions over
 * the internal unlocked counterparts.  This will allow nscd to replace
 * the switch policy state engine with one that uses it's internal
 * components.
 * Only nscd can change this through it's use of nss_config.
 * The golden rule is: ptr == NULL checking is used in the switch to
 * see if a function was interposed.  But nscd is responsible for seeing
 * that mutex locking to change the values are observed when the data is
 * changed.  Especially if it happens > once.  The switch does not lock
 * the pointer with mutexs.
 */

typedef struct {
        void    *p;
#if 0
        void            (*nss_delete_fp)(nss_db_root_t *rootp);
        nss_status_t    (*nss_search_fp)(nss_db_root_t *rootp,
                                nss_db_initf_t initf, int search_fnum,
                                void *search_args);
        void            (*nss_setent_u_fp)(nss_db_root_t *,
                                nss_db_initf_t, nss_getent_t *);
        nss_status_t    (*nss_getent_u_fp)(nss_db_root_t *,
                                nss_db_initf_t, nss_getent_t *, void *);
        void            (*nss_endent_u_fp)(nss_db_root_t *,
                                nss_db_initf_t, nss_getent_t *);
        void            (*end_iter_u_fp)(nss_db_root_t *rootp,
                                struct nss_getent_context *contextp);
#endif
} nss_policyf_t;

static mutex_t nss_policyf_lock = DEFAULTMUTEX;
static nss_policyf_t nss_policyf_ptrs =
        { (void *)NULL };

/*
 * nsswitch db_root state machine definitions:
 * The golden rule is:  if you hold a pointer to an nss_db_state struct and
 * you don't hold the lock, you'd better have incremented the refcount
 * while you held the lock;  otherwise, it may vanish or change
 * significantly when you least expect it.
 *
 * The pointer in nss_db_root_t is one such, so the reference count >= 1.
 * Ditto the pointer in struct nss_getent_context.
 */

/*
 * State for one nsswitch database (e.g. "passwd", "hosts")
 */
struct nss_db_state {
        nss_db_root_t           orphan_root;    /* XXX explain */
        unsigned                refcount;       /* One for the pointer in    */
                                                /*   nss_db_root_t, plus one */
                                                /*   for each active thread. */
        nss_db_params_t         p;
        struct __nsw_switchconfig_v1 *config;
        int                     max_src;        /* is == config->num_lookups */
        struct nss_src_state    *src;           /* Pointer to array[max_src] */
};

/*
 * State for one of the sources (e.g. "nis", "compat") for a database
 */
struct nss_src_state {
        struct __nsw_lookup_v1  *lkp;
        int                     n_active;
        int                     n_dormant;
        int                     n_waiting;      /* ... on wanna_be */
        cond_t                  wanna_be;
        union {
                nss_backend_t   *single; /* Efficiency hack for common case */
                                            /* when limit_dead_backends == 1 */
                nss_backend_t   **multi; /* array[limit_dead_backends] of */
        } dormant;                          /* pointers to dormant backends */
        nss_backend_constr_t    be_constr;
        nss_backend_finder_t    *finder;
        void                    *finder_priv;
};

static struct nss_db_state      *_nss_db_state_constr(nss_db_initf_t);
void                            _nss_db_state_destr(struct nss_db_state *);

/* ==== null definitions if !MTSAFE?  Ditto lock field in nss_db_root_t */

#define NSS_ROOTLOCK(r, sp)     (cancel_safe_mutex_lock(&(r)->lock), \
                                *(sp) = (r)->s)

#define NSS_UNLOCK(r)           (cancel_safe_mutex_unlock(&(r)->lock))

#define NSS_CHECKROOT(rp, s)    ((s) != (*(rp))->s &&                   \
                        (cancel_safe_mutex_unlock(&(*(rp))->lock),      \
                        cancel_safe_mutex_lock(&(s)->orphan_root.lock), \
                        *(rp) = &(s)->orphan_root))

#define NSS_RELOCK(rp, s)       (cancel_safe_mutex_lock(&(*(rp))->lock), \
                        NSS_CHECKROOT(rp, s))

#define NSS_STATE_REF_u(s)      (++(s)->refcount)

#define NSS_UNREF_UNLOCK(r, s)  (--(s)->refcount != 0                   \
                        ? ((void)NSS_UNLOCK(r))                         \
                        : ((void)NSS_UNLOCK(r), (void)_nss_db_state_destr(s)))

#define NSS_LOCK_CHECK(r, f, sp)    (NSS_ROOTLOCK((r), (sp)),   \
                                    *(sp) == 0 &&               \
                                    (r->s = *(sp) = _nss_db_state_constr(f)))
/* === In the future, NSS_LOCK_CHECK() may also have to check that   */
/* === the config info hasn't changed (by comparing version numbers) */


/*
 * NSS_OPTIONS/NIS_OPTIONS environment varibles data definitions:
 * This remains for backwards compatibility.  But generally nscd will
 * decide if/how this gets used.
 */
static int checked_env = 0;             /* protected by "rootlock" */

        /* allowing __nss_debug_file to be set could be a security hole. */
FILE *__nss_debug_file = stdout;
int __nss_debug_eng_loop;

/* NIS_OPTIONS infrastructure (from linbsl/nis/cache/cache_api.cc) */
        /* allowing __nis_debug_file to be set could be a security hole. */
FILE *__nis_debug_file = stdout;
int __nis_debug_bind;
int __nis_debug_rpc;
int __nis_debug_calls;
char *__nis_prefsrv;
char *__nis_preftype;
char *__nis_server;   /* if set, use only this server for binding */

#define OPT_INT 1
#define OPT_STRING 2
#ifdef NSS_DEBUG_INSECURE
#define OPT_FILE 3
#endif

struct option {
        char *name;
        int type;
        void *address;
};

static struct option nss_options[] = {
#ifdef NSS_DEBUG_INSECURE
        /* allowing __nss_debug_file to be set could be a security hole. */
        { "debug_file", OPT_FILE, &__nss_debug_file },
#endif
        { "debug_eng_loop", OPT_INT, &__nss_debug_eng_loop },
        { 0, 0, 0 },
};

static struct option nis_options[] = {
#ifdef NSS_DEBUG_INSECURE
        /* allowing __nis_debug_file to be set could be a security hole. */
        { "debug_file", OPT_FILE, &__nis_debug_file },
#endif
        { "debug_bind", OPT_INT, &__nis_debug_bind },
        { "debug_rpc", OPT_INT, &__nis_debug_rpc },
        { "debug_calls", OPT_INT, &__nis_debug_calls },
        { "server", OPT_STRING, &__nis_server },
        { "pref_srvr", OPT_STRING, &__nis_prefsrv },
        { "pref_type", OPT_STRING, &__nis_preftype },
        { 0, 0, 0 },
};

/*
 * switch configuration parameter "database" definitions:
 * The switch maintains a simmple read/write parameter database
 * that nscd and the switch components can use to communicate
 * nscd data to other components for configuration or out of band
 * [IE no in the context of a getXbyY or putXbyY operation] data.
 * The data passed are pointers to a lock  data buffer and a length.
 * Use of this is treated as SunwPrivate between nscd and the switch
 * unless other wise stated.
 */

typedef struct nss_cfgparam {
        char            *name;
        mutex_t         *lock;
        void            *buffer;
        size_t          length;
} nss_cfgparam_t;

typedef struct nss_cfglist {
        char            *name;
        nss_cfgparam_t  *list;
        int             count;
        int             max;
} nss_cfglist_t;

#define NSS_CFG_INCR    16

static nss_cfglist_t *nss_cfg = NULL;
static int nss_cfgcount = 0;
static int nss_cfgmax = 0;
static mutex_t nss_cfglock = DEFAULTMUTEX;

static int nss_cfg_policy_init();

/*
 * A config parameters are in the form component:parameter
 * as in: nss:parameter - switch (internal FE/policy/BE) parameter
 *        nscd:param - nscd application parameter
 *        ldap:param - nss_ldap BE parameter
 *        passwd:param - get/put passwd FE parameter
 */

#define NSS_CONFIG_BRK  ':'

/*
 * The policy components initial parameter list
 */
static nss_config_t     nss_policy_params[] = {
        { "nss:policyfunc", NSS_CONFIG_ADD, &nss_policyf_lock,
                (void *)&nss_policyf_ptrs, (size_t)sizeof (nss_policyf_t) },
        { NULL, NSS_CONFIG_ADD, (mutex_t *)NULL, (void *)NULL, (size_t)0 },
};

/*
 * NSS parameter configuration routines
 */

/* compare config name (component:parameter) to a component name */
static int
nss_cfgcn_cmp(const char *cfgname, const char *compname)
{
        char *c;
        size_t len, len2;

        /* this code assumes valid pointers */
        if ((c = strchr(cfgname, NSS_CONFIG_BRK)) == NULL)
                return (-1);
        len = (size_t)(c - cfgname);
        len2 = strlen(compname);
        if (len2 != len)
                return (-1);
        return (strncmp(cfgname, compname, len));
}

/* init configuration arena */
static int
nss_cfg_init()
{
        nss_cfglist_t *cfg;
        int i;

        /* First time caller? */
        if (nss_cfg != NULL) {
                membar_consumer();
                return (0);
        }

        /* Initialize internal tables */
        lmutex_lock(&nss_cfglock);
        if (nss_cfg != NULL) {
                lmutex_unlock(&nss_cfglock);
                membar_consumer();
                return (0);
        }
        cfg = libc_malloc(NSS_CFG_INCR * sizeof (nss_cfglist_t));
        if (cfg == NULL) {
                errno = ENOMEM;
                lmutex_unlock(&nss_cfglock);
                return (-1);
        }
        for (i = 0; i < NSS_CFG_INCR; i++) {
                cfg[i].list = libc_malloc(
                    NSS_CFG_INCR * sizeof (nss_cfgparam_t));
                if (cfg[i].list == NULL) {
                        while (--i >= 0)
                                libc_free(cfg[i].list);
                        libc_free(cfg);
                        errno = ENOMEM;
                        lmutex_unlock(&nss_cfglock);
                        return (-1);
                }
                cfg[i].max = NSS_CFG_INCR;
        }
        nss_cfgmax = NSS_CFG_INCR;
        membar_producer();
        nss_cfg = cfg;
        lmutex_unlock(&nss_cfglock);

        /* Initialize Policy Engine values */
        if (nss_cfg_policy_init() < 0) {
                return (-1);
        }
        return (0);
}

/* find the name'd component list - create it if non-existent */
static nss_cfglist_t *
nss_cfgcomp_get(char *name, int add)
{
        nss_cfglist_t   *next;
        char    *c;
        int     i, len;
        size_t  nsize;

        /* Make sure system is init'd */
        if (nss_cfg_init() < 0)
                return ((nss_cfglist_t *)NULL);

        /* and check component:name validity */
        if (name == NULL || (c = strchr(name, NSS_CONFIG_BRK)) == NULL)
                return ((nss_cfglist_t *)NULL);

        lmutex_lock(&nss_cfglock);
        next = nss_cfg;
        for (i = 0; i < nss_cfgcount; i++) {
                if (next->name && nss_cfgcn_cmp(name, next->name) == 0) {
                        lmutex_unlock(&nss_cfglock);
                        return (next);
                }
                next++;
        }
        if (!add) {
                lmutex_unlock(&nss_cfglock);
                return (NULL);
        }

        /* not found, create a fresh one */
        if (nss_cfgcount >= nss_cfgmax) {
                /* realloc first */
                nsize = (nss_cfgmax + NSS_CFG_INCR) * sizeof (nss_cfgparam_t);
                next = (nss_cfglist_t *)libc_realloc(nss_cfg, nsize);
                if (next == NULL) {
                        errno = ENOMEM;
                        lmutex_unlock(&nss_cfglock);
                        return ((nss_cfglist_t *)NULL);
                }
                (void) memset((void *)(next + nss_cfgcount), '\0',
                    NSS_CFG_INCR * sizeof (nss_cfglist_t));
                nss_cfgmax += NSS_CFG_INCR;
                nss_cfg = next;
        }
        next = nss_cfg + nss_cfgcount;
        len = (size_t)(c - name) + 1;
        if ((next->name = libc_malloc(len)) == NULL) {
                errno = ENOMEM;
                lmutex_unlock(&nss_cfglock);
                return ((nss_cfglist_t *)NULL);
        }
        nss_cfgcount++;
        (void) strlcpy(next->name, name, len);
        lmutex_unlock(&nss_cfglock);
        return (next);
}

/* find the name'd parameter - create it if non-existent */
static nss_cfgparam_t *
nss_cfgparam_get(char *name, int add)
{
        nss_cfglist_t   *comp;
        nss_cfgparam_t  *next;
        int     count, i;
        size_t  nsize;

        if ((comp = nss_cfgcomp_get(name, add)) == NULL)
                return ((nss_cfgparam_t *)NULL);
        lmutex_lock(&nss_cfglock);
        count = comp->count;
        next = comp->list;
        for (i = 0; i < count; i++) {
                if (next->name && strcmp(name, next->name) == 0) {
                        lmutex_unlock(&nss_cfglock);
                        return (next);
                }
                next++;
        }
        if (!add) {
                lmutex_unlock(&nss_cfglock);
                return (NULL);
        }

        /* not found, create a fresh one */
        if (count >= comp->max) {
                /* realloc first */
                nsize = (comp->max + NSS_CFG_INCR) * sizeof (nss_cfgparam_t);
                next = (nss_cfgparam_t *)libc_realloc(comp->list, nsize);
                if (next == NULL) {
                        errno = ENOMEM;
                        lmutex_unlock(&nss_cfglock);
                        return ((nss_cfgparam_t *)NULL);
                }
                comp->max += NSS_CFG_INCR;
                comp->list = next;
        }
        next = comp->list + comp->count;
        if ((next->name = libc_strdup(name)) == NULL) {
                errno = ENOMEM;
                lmutex_unlock(&nss_cfglock);
                return ((nss_cfgparam_t *)NULL);
        }
        comp->count++;
        lmutex_unlock(&nss_cfglock);
        return (next);
}

/* find the name'd parameter - delete it if it exists */
static void
nss_cfg_del(nss_config_t *cfgp)
{
        char            *name;
        nss_cfglist_t   *comp;
        nss_cfgparam_t  *next, *cur;
        int     count, i, j;

        /* exit if component name does not already exist */
        if ((name = cfgp->name) == NULL ||
            (comp = nss_cfgcomp_get(name, 0)) == NULL)
                return;

        /* find it */
        lmutex_lock(&nss_cfglock);
        count = comp->count;
        next = comp->list;
        for (i = 0; i < count; i++) {
                if (next->name && strcmp(name, next->name) == 0) {
                        break;  /* found it... */
                }
                next++;
        }
        if (i >= count) {
                /* not found, already deleted */
                lmutex_unlock(&nss_cfglock);
                return;
        }

        /* copy down the remaining parameters, and clean up */
        /* don't try to clean up component tables */
        cur = next;
        next++;
        for (j = i+1; j < count; j++) {
                *cur = *next;
                cur++;
                next++;
        }
        /* erase the last one */
        if (cur->name) {
                libc_free(cur->name);
                cur->name = (char *)NULL;
        }
        cur->lock = (mutex_t *)NULL;
        cur->buffer = (void *)NULL;
        cur->length = 0;
        comp->count--;
        lmutex_unlock(&nss_cfglock);
}

static int
nss_cfg_get(nss_config_t *next)
{
        nss_cfgparam_t  *param;

        errno = 0;
        if ((param = nss_cfgparam_get(next->name, 0)) == NULL)
                return (-1);
        next->lock = param->lock;
        next->buffer = param->buffer;
        next->length = param->length;
        return (0);
}

static int
nss_cfg_put(nss_config_t *next, int add)
{
        nss_cfgparam_t  *param;

        errno = 0;
        if ((param = nss_cfgparam_get(next->name, add)) == NULL)
                return (-1);
        param->lock = next->lock;
        param->buffer = next->buffer;
        param->length = next->length;
        return (0);
}

/*
 * Policy engine configurator - set and get interface
 * argument is a NULL terminated list of set/get requests
 * with input/result buffers and lengths.  nss_cname is the
 * specifier of a set or get operation and the property being
 * managed.  The intent is limited functions and expandability.
 */

nss_status_t
nss_config(nss_config_t **plist, int cnt)
{
        nss_config_t    *next;
        int     i;

        /* interface is only available to nscd */
        if (_nsc_proc_is_cache() <= 0) {
                return (NSS_UNAVAIL);
        }
        if (plist == NULL || cnt <= 0)
                return (NSS_SUCCESS);
        for (i = 0; i < cnt; i++) {
                next = plist[i];
                if (next == NULL)
                        break;
                if (next->name == NULL) {
                        errno = EFAULT;
                        return (NSS_ERROR);
                }
                switch (next->cop) {
                case NSS_CONFIG_GET:
                        /* get current lock/buffer/length fields */
                        if (nss_cfg_get(next) < 0) {
                                return (NSS_ERROR);
                        }
                        break;
                case NSS_CONFIG_PUT:
                        /* set new lock/buffer/length fields */
                        if (nss_cfg_put(next, 0) < 0) {
                                return (NSS_ERROR);
                        }
                        break;
                case NSS_CONFIG_ADD:
                        /* add parameter & set new lock/buffer/length fields */
                        if (nss_cfg_put(next, 1) < 0) {
                                return (NSS_ERROR);
                        }
                        break;
                case NSS_CONFIG_DELETE:
                        /* delete parameter - should always work... */
                        nss_cfg_del(next);
                        break;
                case NSS_CONFIG_LIST:
                        break;
                default:
                        continue;
                }
        }
        return (NSS_SUCCESS);
}

/*
 * This routine is called immediately after nss_cfg_init but prior to
 * any commands from nscd being processed.  The intent here is to
 * initialize the nss:* parameters allowed by the policy component
 * so that nscd can then proceed and modify them if so desired.
 *
 * We know we can only get here if we are nscd so we can skip the
 * preliminaries.
 */

static int
nss_cfg_policy_init()
{
        nss_config_t    *next = &nss_policy_params[0];

        for (; next && next->name != NULL; next++) {
                if (nss_cfg_put(next, 1) < 0)
                        return (-1);
        }
        return (0);
}

/*
 * NSS_OPTION & NIS_OPTION environment variable functions
 */

static
void
set_option(struct option *opt, char *name, char *val)
{
        int n;
        char *p;
#ifdef NSS_DEBUG_INSECURE
        FILE *fp;
#endif

        for (; opt->name; opt++) {
                if (strcmp(name, opt->name) == 0) {
                        switch (opt->type) {
                        case OPT_STRING:
                                p = libc_strdup(val);
                                *((char **)opt->address) = p;
                                break;

                        case OPT_INT:
                                if (strcmp(val, "") == 0)
                                        n = 1;
                                else
                                        n = atoi(val);
                                *((int *)opt->address) = n;
                                break;
#ifdef NSS_DEBUG_INSECURE
                        case OPT_FILE:
                                fp = fopen(val, "wF");
                                *((FILE **)opt->address) = fp;
                                break;
#endif
                        }
                        break;
                }
        }
}

static
void
__parse_environment(struct option *opt, char *p)
{
        char *base;
        char optname[100];
        char optval[100];

        while (*p) {
                while (isspace(*p))
                        p++;
                if (*p == '\0')
                        break;

                base = p;
                while (*p && *p != '=' && !isspace(*p))
                        p++;
                /*
                 * play it safe and keep it simple, bail if an opt name
                 * is too long.
                 */
                if ((p-base) >= sizeof (optname))
                        return;

                (void) strncpy(optname, base, p-base);
                optname[p-base] = '\0';

                if (*p == '=') {
                        p++;
                        base = p;
                        while (*p && !isspace(*p))
                                p++;
                        /*
                         * play it safe and keep it simple, bail if an opt
                         * value is too long.
                         */
                        if ((p-base) >= sizeof (optval))
                                return;

                        (void) strncpy(optval, base, p-base);
                        optval[p-base] = '\0';
                } else {
                        optval[0] = '\0';
                }

                set_option(opt, optname, optval);
        }
}

static
void
nss_get_environment()
{
        char *p;

/* NSS_OPTIONS is undocumented and should be used without nscd running. */
        p = getenv("NSS_OPTIONS");
        if (p == NULL)
                return;
        __parse_environment(nss_options, p);
}

/*
 * sole external routine called from libnsl/nis/cache/cache_api.cc in the
 * routines _nis_CacheInit/__nis_CacheLocalInit/__nis_CacheMgrInit_discard
 * Only after checking "checked_env" (which must be done with mutex
 * "cur_cache_lock" held) and is done once, (then "checked_env" is set)
 */
void
__nis_get_environment()
{
        char *p;

        p = getenv("NIS_OPTIONS");
        if (p == NULL)
                return;
        __parse_environment(nis_options, p);
}


/*
 * Switch policy component backend state machine functions
 */

static nss_backend_t *
nss_get_backend_u(nss_db_root_t **rootpp, struct nss_db_state *s, int n_src)
{
        struct nss_src_state    *src = &s->src[n_src];
        nss_backend_t           *be;
        int cancel_state;

        for (;;) {
                if (src->n_dormant > 0) {
                        src->n_dormant--;
                        src->n_active++;
                        if (s->p.max_dormant_per_src == 1) {
                                be = src->dormant.single;
                        } else {
                                be = src->dormant.multi[src->n_dormant];
                        }
                        break;
                }

                if (src->be_constr == 0) {
                        nss_backend_finder_t    *bf;

                        for (bf = s->p.finders;  bf != 0;  bf = bf->next) {
                                nss_backend_constr_t c;

                                c = (*bf->lookup) (bf->lookup_priv, s->p.name,
                                    src->lkp->service_name, &src->finder_priv);
                                if (c != 0) {
                                        src->be_constr = c;
                                        src->finder = bf;
                                        break;
                                }
                        }
                        if (src->be_constr == 0) {
                                /* Couldn't find the backend anywhere */
                                be = 0;
                                break;
                        }
                }

                if (src->n_active < s->p.max_active_per_src) {
                        be = (*src->be_constr)(s->p.name,
                            src->lkp->service_name, 0 /* === unimplemented */);
                        if (be != 0) {
                                src->n_active++;
                                break;
                        } else if (src->n_active == 0) {
                                /* Something's wrong;  we should be */
                                /*   able to create at least one    */
                                /*   instance of the backend        */
                                break;
                        }
                        /*
                         * Else it's odd that we can't create another backend
                         *   instance, but don't sweat it;  instead, queue for
                         *   an existing backend instance.
                         */
                }

                src->n_waiting++;
                (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,
                    &cancel_state);
                (void) cond_wait(&src->wanna_be, &(*rootpp)->lock);
                (void) pthread_setcancelstate(cancel_state, NULL);
                NSS_CHECKROOT(rootpp, s);
                src->n_waiting--;

                /*
                 * Loop and see whether things got better for us, or whether
                 *   someone else got scheduled first and we have to try
                 *   this again.
                 *
                 * === ?? Should count iterations, assume bug if many ??
                 */
        }
        return (be);
}

static void
nss_put_backend_u(struct nss_db_state *s, int n_src, nss_backend_t *be)
{
        struct nss_src_state    *src = &s->src[n_src];

        if (be == 0) {
                return;
        }

        src->n_active--;

        if (src->n_dormant < s->p.max_dormant_per_src) {
                if (s->p.max_dormant_per_src == 1) {
                        src->dormant.single = be;
                        src->n_dormant++;
                } else if (src->dormant.multi != 0 ||
                    (src->dormant.multi =
                    libc_malloc(s->p.max_dormant_per_src *
                    sizeof (nss_backend_t *))) != NULL) {
                        src->dormant.multi[src->n_dormant] = be;
                        src->n_dormant++;
                } else {
                        /* Can't store it, so toss it */
                        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_DESTRUCTOR, 0);
                }
        } else {
                /* We've stored as many as we want, so toss it */
                (void) NSS_INVOKE_DBOP(be, NSS_DBOP_DESTRUCTOR, 0);
        }
        if (src->n_waiting > 0) {
                (void) cond_signal(&src->wanna_be);
        }
}

static struct nss_db_state *
_nss_db_state_constr(nss_db_initf_t initf)
{
        struct nss_db_state     *s;
        struct __nsw_switchconfig_v1 *config = 0;
        struct __nsw_lookup_v1  *lkp;
        enum __nsw_parse_err    err;
        const char              *config_name;
        int                     n_src;

        if ((s = libc_malloc(sizeof (*s))) == 0) {
                return (0);
        }
        (void) mutex_init(&s->orphan_root.lock, USYNC_THREAD, 0);

        s->p.max_active_per_src = 10;
        s->p.max_dormant_per_src = 1;
        s->p.finders = nss_default_finders;
        (*initf)(&s->p);
        if (s->p.name == 0) {
                _nss_db_state_destr(s);
                return (0);
        }

        if (!checked_env) {
/* NSS_OPTIONS is undocumented and should be used without nscd running. */
                nss_get_environment();
                checked_env = 1;
        }

        config_name = s->p.config_name ? s->p.config_name : s->p.name;
        if (! (s->p.flags & NSS_USE_DEFAULT_CONFIG)) {
                config = __nsw_getconfig_v1(config_name, &err);
                /* === ? test err ? */
        }
        if (config == 0) {
                /* getconfig failed, or frontend demanded default config */

                char    *str;   /* _nsw_getoneconfig() clobbers its argument */

                if ((str = libc_strdup(s->p.default_config)) != 0) {
                        config = _nsw_getoneconfig_v1(config_name, str, &err);
                        libc_free(str);
                }
                if (config == 0) {
                        _nss_db_state_destr(s);
                        return (0);
                }
        }
        s->config = config;
        if ((s->max_src = config->num_lookups) <= 0 ||
            (s->src = libc_malloc(s->max_src * sizeof (*s->src))) == 0) {
                _nss_db_state_destr(s);
                return (0);
        }
        for (n_src = 0, lkp = config->lookups;
            n_src < s->max_src; n_src++, lkp = lkp->next) {
                s->src[n_src].lkp = lkp;
                (void) cond_init(&s->src[n_src].wanna_be, USYNC_THREAD, 0);
        }
        s->refcount = 1;
        return (s);
}

void
_nss_src_state_destr(struct nss_src_state *src, int max_dormant)
{
        if (max_dormant == 1) {
                if (src->n_dormant != 0) {
                        (void) NSS_INVOKE_DBOP(src->dormant.single,
                            NSS_DBOP_DESTRUCTOR, 0);
                };
        } else if (src->dormant.multi != 0) {
                int     n;

                for (n = 0;  n < src->n_dormant;  n++) {
                        (void) NSS_INVOKE_DBOP(src->dormant.multi[n],
                            NSS_DBOP_DESTRUCTOR, 0);
                }
                libc_free(src->dormant.multi);
        }

        /* cond_destroy(&src->wanna_be); */

        if (src->finder != 0) {
                (*src->finder->delete)(src->finder_priv, src->be_constr);
        }
}

/*
 * _nss_db_state_destr() -- used by NSS_UNREF_UNLOCK() to free the entire
 *      nss_db_state structure.
 * Assumes that s has been ref-counted down to zero (in particular,
 *      rootp->s has already been dealt with).
 *
 * Nobody else holds a pointer to *s (if they did, refcount != 0),
 *   so we can clean up state *after* we drop the lock (also, by the
 *   time we finish freeing the state structures, the lock may have
 *   ceased to exist -- if we were using the orphan_root).
 */

void
_nss_db_state_destr(struct nss_db_state *s)
{
        if (s == NULL)
                return;

        /* === mutex_destroy(&s->orphan_root.lock); */
        if (s->p.cleanup != 0) {
                (*s->p.cleanup)(&s->p);
        }
        if (s->config != 0) {
                (void) __nsw_freeconfig_v1(s->config);
        }
        if (s->src != 0) {
                int     n_src;

                for (n_src = 0;  n_src < s->max_src;  n_src++) {
                        _nss_src_state_destr(&s->src[n_src],
                            s->p.max_dormant_per_src);
                }
                libc_free(s->src);
        }
        libc_free(s);
}


/*
 * _nss_status_vec() returns a bit vector of all status codes returned during
 * the most recent call to nss_search().
 * _nss_status_vec_p() returns a pointer to this bit vector, or NULL on
 * failure.
 * These functions are private.  Don't use them externally without discussing
 * it with the switch maintainers.
 */
static uint_t *
_nss_status_vec_p()
{
        return (tsdalloc(_T_NSS_STATUS_VEC, sizeof (uint_t), NULL));
}

unsigned int
_nss_status_vec(void)
{
        unsigned int *status_vec_p = _nss_status_vec_p();

        return ((status_vec_p != NULL) ? *status_vec_p : (1 << NSS_UNAVAIL));
}

static void
output_loop_diag_a(int n,
    char *dbase,
    struct __nsw_lookup_v1 *lkp)
{
        (void) fprintf(__nss_debug_file,
            "NSS_retry(%d): '%s': trying '%s' ... ",
            n, dbase, lkp->service_name);
        (void) fflush(__nss_debug_file);

}

static void
output_loop_diag_b(nss_status_t res,
    struct __nsw_lookup_v1 *lkp)
{
        (void) fprintf(__nss_debug_file, "result=");
        switch (res) {
        case NSS_SUCCESS:
                (void) fprintf(__nss_debug_file, "SUCCESS");
                break;
        case NSS_NOTFOUND:
                (void) fprintf(__nss_debug_file, "NOTFOUND");
                break;
        case NSS_UNAVAIL:
                (void) fprintf(__nss_debug_file, "UNAVAIL");
                break;
        case NSS_TRYAGAIN:
                (void) fprintf(__nss_debug_file, "TRYAGAIN");
                break;
        case NSS_NISSERVDNS_TRYAGAIN:
                (void) fprintf(__nss_debug_file, "NISSERVDNS_TRYAGAIN");
                break;
        default:
                (void) fprintf(__nss_debug_file, "undefined");
        }
        (void) fprintf(__nss_debug_file, ", action=");
        switch (lkp->actions[res]) {
        case __NSW_CONTINUE:
                (void) fprintf(__nss_debug_file, "CONTINUE");
                break;
        case  __NSW_RETURN:
                (void) fprintf(__nss_debug_file, "RETURN");
                break;
        case __NSW_TRYAGAIN_FOREVER:
                (void) fprintf(__nss_debug_file, "TRYAGAIN_FOREVER");
                break;
        case __NSW_TRYAGAIN_NTIMES:
                (void) fprintf(__nss_debug_file, "TRYAGAIN_NTIMES (N=%d)",
                    lkp->max_retries);
                break;
        case __NSW_TRYAGAIN_PAUSED:
                (void) fprintf(__nss_debug_file, "TRYAGAIN_PAUSED");
                break;
        default:
                (void) fprintf(__nss_debug_file, "undefined");
        }
        (void) fprintf(__nss_debug_file, "\n");
}

#define NSS_BACKOFF(n, b, t) \
                        ((n) > ((b) + 3) ? t : (1 << ((n) - ((b) + 1))))

static int
retry_test(nss_status_t res, int n, struct __nsw_lookup_v1 *lkp)
{
        if (res != NSS_TRYAGAIN && res !=  NSS_NISSERVDNS_TRYAGAIN) {
                if (res == NSS_SUCCESS) {
                        __NSW_UNPAUSE_ACTION(lkp->actions[__NSW_TRYAGAIN]);
                        __NSW_UNPAUSE_ACTION(
                            lkp->actions[__NSW_NISSERVDNS_TRYAGAIN]);
                }
                return (0);
        }

        if ((res == NSS_TRYAGAIN &&
            lkp->actions[__NSW_TRYAGAIN] == __NSW_TRYAGAIN_FOREVER) ||
            (res == NSS_NISSERVDNS_TRYAGAIN &&
            lkp->actions[__NSW_NISSERVDNS_TRYAGAIN] == __NSW_TRYAGAIN_FOREVER))
                return (1);

        if (res == NSS_TRYAGAIN &&
            lkp->actions[__NSW_TRYAGAIN] == __NSW_TRYAGAIN_NTIMES)
                if (n <= lkp->max_retries)
                        return (1);
                else {
                        lkp->actions[__NSW_TRYAGAIN] = __NSW_TRYAGAIN_PAUSED;
                        return (0);
                }

        if (res == NSS_NISSERVDNS_TRYAGAIN &&
            lkp->actions[__NSW_NISSERVDNS_TRYAGAIN] == __NSW_TRYAGAIN_NTIMES)
                if (n <= lkp->max_retries)
                        return (1);
                else {
                        lkp->actions[__NSW_NISSERVDNS_TRYAGAIN] =
                            __NSW_TRYAGAIN_PAUSED;
                        return (0);
                }

        return (0);
}

/*
 * Switch policy component functional interfaces
 */

void
nss_delete(nss_db_root_t *rootp)
{
        struct nss_db_state     *s;

        /* no name service cache daemon divert here */
        /* local nss_delete decrements state reference counts */
        /* and may free up opened switch resources. */

        NSS_ROOTLOCK(rootp, &s);
        if (s == 0) {
                NSS_UNLOCK(rootp);
        } else {
                rootp->s = 0;
                NSS_UNREF_UNLOCK(rootp, s);
        }
}

nss_status_t
nss_search(nss_db_root_t *rootp, nss_db_initf_t initf, int search_fnum,
    void *search_args)
{
        nss_status_t            res = NSS_UNAVAIL;
        struct nss_db_state     *s;
        int                     n_src;
        unsigned int            *status_vec_p;

        /* name service cache daemon divert */
        res = _nsc_search(rootp, initf, search_fnum, search_args);
        if (res != NSS_TRYLOCAL)
                return (res);

        /* fall through - process locally */
        errno = 0;                      /* just in case ... */
        res = NSS_UNAVAIL;
        status_vec_p = _nss_status_vec_p();

        if (status_vec_p == NULL) {
                return (NSS_UNAVAIL);
        }
        *status_vec_p = 0;

        NSS_LOCK_CHECK(rootp, initf, &s);
        if (s == 0) {
                NSS_UNLOCK(rootp);
                return (res);
        }
        NSS_STATE_REF_u(s);

        for (n_src = 0;  n_src < s->max_src;  n_src++) {
                nss_backend_t           *be;
                nss_backend_op_t        funcp;

                res = NSS_UNAVAIL;
                if ((be = nss_get_backend_u(&rootp, s, n_src)) != 0) {
                        if ((funcp = NSS_LOOKUP_DBOP(be, search_fnum)) != 0) {
                                int n_loop = 0;
                                int no_backoff = 19;
                                int max_backoff = 5;    /* seconds */

                                do {
                                        /*
                                         * Backend operation may take a while;
                                         * drop the lock so we don't serialize
                                         * more than necessary.
                                         */
                                        NSS_UNLOCK(rootp);

                                        /* After several tries, backoff... */
                                        if (n_loop > no_backoff) {
                                                if (__nss_debug_eng_loop > 1)
                                                        (void) fprintf(
                                                            __nss_debug_file,
                                                            "NSS: loop: "
                                                            "sleeping %d ...\n",
                                                            NSS_BACKOFF(n_loop,
                                                            no_backoff,
                                                            max_backoff));

                                                (void) sleep(NSS_BACKOFF(n_loop,
                                                    no_backoff, max_backoff));
                                        }

                                        if (__nss_debug_eng_loop)
                                                output_loop_diag_a(n_loop,
                                                    s->config->dbase,
                                                    s->src[n_src].lkp);


                                        res = (*funcp)(be, search_args);
                                        NSS_RELOCK(&rootp, s);
                                        n_loop++;
                                        if (__nss_debug_eng_loop)
                                                output_loop_diag_b(res,
                                                    s->src[n_src].lkp);
                                } while (retry_test(res, n_loop,
                                    s->src[n_src].lkp));
                        }
                        nss_put_backend_u(s, n_src, be);
                }
                *status_vec_p |= (1 << res);
                if (__NSW_ACTION_V1(s->src[n_src].lkp, res) == __NSW_RETURN) {
                        if (__nss_debug_eng_loop)
                                (void) fprintf(__nss_debug_file,
                                    "NSS: '%s': return.\n",
                                    s->config->dbase);
                        break;
                } else
                        if (__nss_debug_eng_loop)
                                (void) fprintf(__nss_debug_file,
                                    "NSS: '%s': continue ...\n",
                                    s->config->dbase);
        }
        NSS_UNREF_UNLOCK(rootp, s);
        return (res);
}


/*
 * Start of nss_{setent|getent|endent}
 */

/*
 * State (here called "context") for one setent/getent.../endent sequence.
 *   In principle there could be multiple contexts active for a single
 *   database;  in practice, since Posix and UI have helpfully said that
 *   getent() state is global rather than, say, per-thread or user-supplied,
 *   we have at most one of these per nss_db_state.
 *   XXX ? Is this statement still true?
 *
 * NSS2 - a client's context is maintained as a cookie delivered by and
 * passed to nscd.  The cookie is a 64 bit (nssuint_t) unique opaque value
 * created by nscd.
 * cookie states:
 *      NSCD_NEW_COOKIE         - cookie value uninitialized
 *      NSCD_LOCAL_COOKIE       - setent is a local setent
 *      all other               - NSCD unique opaque id for this setent
 * A client's context is also associated with a seq_num.  This is a nscd
 * opaque 64 bit (nssuint_t) value passed with a cookie, and used to by nscd
 * to validate the sequencing of the context.  The client treats this as
 * a pass through value.
 *
 * XXX ??  Use Cookie as cross-check info so that we can detect an
 * nss_context that missed an nss_delete() or similar.
 */

struct nss_getent_context {
        int                     n_src;  /* >= max_src ==> end of sequence */
        nss_backend_t           *be;
        struct nss_db_state     *s;
        nssuint_t               cookie;
        nssuint_t               seq_num;
        nssuint_t               cookie_setent;
        nss_db_params_t         param;
};

static void nss_setent_u(nss_db_root_t *, nss_db_initf_t, nss_getent_t *);
static nss_status_t nss_getent_u(nss_db_root_t *, nss_db_initf_t,
    nss_getent_t *, void *);
static void nss_endent_u(nss_db_root_t *, nss_db_initf_t, nss_getent_t *);

void
nss_setent(nss_db_root_t *rootp, nss_db_initf_t initf, nss_getent_t *contextpp)
{
        if (contextpp == 0) {
                return;
        }
        cancel_safe_mutex_lock(&contextpp->lock);
        nss_setent_u(rootp, initf, contextpp);
        cancel_safe_mutex_unlock(&contextpp->lock);
}

nss_status_t
nss_getent(nss_db_root_t *rootp, nss_db_initf_t initf, nss_getent_t *contextpp,
    void *args)
{
        nss_status_t            status;

        if (contextpp == 0) {
                return (NSS_UNAVAIL);
        }
        cancel_safe_mutex_lock(&contextpp->lock);
        status = nss_getent_u(rootp, initf, contextpp, args);
        cancel_safe_mutex_unlock(&contextpp->lock);
        return (status);
}

void
nss_endent(nss_db_root_t *rootp, nss_db_initf_t initf, nss_getent_t *contextpp)
{
        if (contextpp == 0) {
                return;
        }
        cancel_safe_mutex_lock(&contextpp->lock);
        nss_endent_u(rootp, initf, contextpp);
        cancel_safe_mutex_unlock(&contextpp->lock);
}

/*
 * Each of the _u versions of the nss interfaces assume that the context
 * lock is held.  No need to divert to nscd.  Private to local sequencing.
 */

static void
end_iter_u(nss_db_root_t *rootp, struct nss_getent_context *contextp)
{
        struct nss_db_state     *s;
        nss_backend_t           *be;
        int                     n_src;

        s = contextp->s;
        n_src = contextp->n_src;
        be = contextp->be;

        if (s != 0) {
                if (n_src < s->max_src && be != 0) {
                        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_ENDENT, 0);
                        NSS_RELOCK(&rootp, s);
                        nss_put_backend_u(s, n_src, be);
                        contextp->be = 0;  /* Should be unnecessary, but hey */
                        NSS_UNREF_UNLOCK(rootp, s);
                }
                contextp->s = 0;
        }
}

static void
nss_setent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp)
{
        nss_status_t            status;
        struct nss_db_state     *s;
        struct nss_getent_context *contextp;
        nss_backend_t           *be;
        int                     n_src;

        /* setup process wide context while locked */
        if ((contextp = contextpp->ctx) == 0) {
                if ((contextp = libc_malloc(sizeof (*contextp))) == 0) {
                        return;
                }
                contextpp->ctx = contextp;
                contextp->cookie = NSCD_NEW_COOKIE;     /* cookie init */
                contextp->seq_num = 0;                  /* seq_num init */
                s = 0;
        } else {
                s = contextp->s;
                if (contextp->cookie != NSCD_LOCAL_COOKIE)
                        contextp->cookie = NSCD_NEW_COOKIE;
        }

        /* name service cache daemon divert */
        if (contextp->cookie == NSCD_NEW_COOKIE) {
                status = _nsc_setent_u(rootp, initf, contextpp);
                if (status != NSS_TRYLOCAL)
                        return;
        }

        /* fall through - process locally */
        if (s == 0) {
                NSS_LOCK_CHECK(rootp, initf, &s);
                if (s == 0) {
                        /* Couldn't set up state, so quit */
                        NSS_UNLOCK(rootp);
                        /* ==== is there any danger of not having done an */
                        /* end_iter() here, and hence of losing backends? */
                        contextpp->ctx = 0;
                        libc_free(contextp);
                        return;
                }
                NSS_STATE_REF_u(s);
                contextp->s = s;
        } else {
                s       = contextp->s;
                n_src   = contextp->n_src;
                be      = contextp->be;
                if (n_src == 0 && be != 0) {
                        /*
                         * Optimization:  don't do endent, don't change
                         *   backends, just do the setent.  Look Ma, no locks
                         *   (nor any context that needs updating).
                         */
                        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_SETENT, 0);
                        return;
                }
                if (n_src < s->max_src && be != 0) {
                        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_ENDENT, 0);
                        NSS_RELOCK(&rootp, s);
                        nss_put_backend_u(s, n_src, be);
                        contextp->be = 0;       /* Play it safe */
                } else {
                        NSS_RELOCK(&rootp, s);
                }
        }
        for (n_src = 0, be = 0; n_src < s->max_src &&
            (be = nss_get_backend_u(&rootp, s, n_src)) == 0; n_src++) {
                ;
        }
        NSS_UNLOCK(rootp);

        contextp->n_src = n_src;
        contextp->be    = be;

        if (be == 0) {
                /* Things are broken enough that we can't do setent/getent */
                nss_endent_u(rootp, initf, contextpp);
                return;
        }
        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_SETENT, 0);
}

static nss_status_t
nss_getent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp, void *args)
{
        nss_status_t            status;
        struct nss_db_state     *s;
        struct nss_getent_context *contextp;
        int                     n_src;
        nss_backend_t           *be;

        if ((contextp = contextpp->ctx) == 0) {
                nss_setent_u(rootp, initf, contextpp);
                if ((contextp = contextpp->ctx) == 0) {
                        /* Give up */
                        return (NSS_UNAVAIL);
                }
        }
        /* name service cache daemon divert */
        status = _nsc_getent_u(rootp, initf, contextpp, args);
        if (status != NSS_TRYLOCAL)
                return (status);

        /* fall through - process locally */
        s       = contextp->s;
        n_src   = contextp->n_src;
        be      = contextp->be;

        if (s == 0) {
                /*
                 * We've done an end_iter() and haven't done nss_setent()
                 * or nss_endent() since;  we should stick in this state
                 * until the caller invokes one of those two routines.
                 */
                return (NSS_SUCCESS);
        }

        while (n_src < s->max_src) {
                nss_status_t res;

                if (be == 0) {
                        /* If it's null it's a bug, but let's play safe */
                        res = NSS_UNAVAIL;
                } else {
                        res = NSS_INVOKE_DBOP(be, NSS_DBOP_GETENT, args);
                }

                if (__NSW_ACTION_V1(s->src[n_src].lkp, res) == __NSW_RETURN) {
                        if (res != __NSW_SUCCESS) {
                                end_iter_u(rootp, contextp);
                        }
                        return (res);
                }
                (void) NSS_INVOKE_DBOP(be, NSS_DBOP_ENDENT, 0);
                NSS_RELOCK(&rootp, s);
                nss_put_backend_u(s, n_src, be);
                do {
                        n_src++;
                } while (n_src < s->max_src &&
                    (be = nss_get_backend_u(&rootp, s, n_src)) == 0);
                contextp->be = be;
                if (be == 0) {
                        /*
                         * This is the case where we failed to get the backend
                         * for the last source. We exhausted all sources.
                         *
                         * We need to do cleanup ourselves because end_iter_u()
                         * does not do it for be == 0.
                         */
                        NSS_UNREF_UNLOCK(rootp, s);
                        contextp->s = 0;
                        break;
                } else {
                        NSS_UNLOCK(rootp);
                        contextp->n_src = n_src;
                        (void) NSS_INVOKE_DBOP(be, NSS_DBOP_SETENT, 0);
                }
        }
        /* Got to the end of the sources without finding another entry */
        end_iter_u(rootp, contextp);
        return (NSS_SUCCESS);
        /* success is either a successful entry or end of the sources */
}

/*ARGSUSED*/
static void
nss_endent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp)
{
        nss_status_t            status;
        struct nss_getent_context *contextp;

        if ((contextp = contextpp->ctx) == 0) {
                /* nss_endent() on an unused context is a no-op */
                return;
        }

        /* notify name service cache daemon */
        status = _nsc_endent_u(rootp, initf, contextpp);
        if (status != NSS_TRYLOCAL) {
                /* clean up */
                libc_free(contextp);
                contextpp->ctx = 0;
                return;
        }

        /* fall through - process locally */

        /*
         * Existing code (BSD, SunOS) works in such a way that getXXXent()
         *   following an endXXXent() behaves as though the user had invoked
         *   setXXXent(), i.e. it iterates properly from the beginning.
         * We'd better not break this, so our choices are
         *      (1) leave the context structure around, and do nss_setent or
         *          something equivalent,
         *   or (2) free the context completely, and rely on the code in
         *          nss_getent() that makes getXXXent() do the right thing
         *          even without a preceding setXXXent().
         * The code below does (2), which frees up resources nicely but will
         * cost more if the user then does more getXXXent() operations.
         * Moral:  for efficiency, don't call endXXXent() prematurely.
         */
        end_iter_u(rootp, contextp);
        libc_free(contextp);
        contextpp->ctx = 0;
}

/*
 * pack dbd data into header
 * Argment pointers assumed valid.
 * poff offset position pointer
 *   IN = starting offset for dbd header
 *   OUT = starting offset for next section
 */

static nss_status_t
nss_pack_dbd(void *buffer, size_t bufsize, nss_db_params_t *p, size_t *poff)
{
        nss_pheader_t           *pbuf = (nss_pheader_t *)buffer;
        nss_dbd_t               *pdbd;
        size_t                  off = *poff;
        size_t                  len, blen;
        size_t                  n, nc, dc;
        char                    *bptr;

        pbuf->dbd_off = (nssuint_t)off;
        bptr = (char *)buffer + off;
        blen = bufsize - off;
        len = sizeof (nss_dbd_t);

        n = nc = dc = 0;
        if (p->name == NULL) {
                errno = ERANGE;                 /* actually EINVAL */
                return (NSS_ERROR);
        }

        /* if default config not specified, the flag should be reset */
        if (p->default_config == NULL) {
                p->default_config = "<NULL>";
                p->flags = p->flags & ~NSS_USE_DEFAULT_CONFIG;
        }

        n = strlen(p->name) + 1;
        dc = strlen(p->default_config) + 1;
        if (n < 2 || dc < 2) {                  /* What no DB? */
                errno = ERANGE;                 /* actually EINVAL */
                return (NSS_ERROR);
        }
        if (p->config_name != NULL) {
                nc = strlen(p->config_name) + 1;
        }
        if ((len + n + nc + dc) >= blen) {
                errno = ERANGE;                 /* actually EINVAL */
                return (NSS_ERROR);
        }

        pdbd = (nss_dbd_t *)((void *)bptr);
        bptr += len;
        pdbd->flags = p->flags;
        pdbd->o_name = len;
        (void) strlcpy(bptr, p->name, n);
        len += n;
        bptr += n;
        if (nc == 0) {
                pdbd->o_config_name = 0;
        } else {
                pdbd->o_config_name = len;
                (void) strlcpy(bptr, p->config_name, nc);
                bptr += nc;
                len += nc;
        }
        pdbd->o_default_config = len;
        (void) strlcpy(bptr, p->default_config, dc);
        len += dc;
        pbuf->dbd_len = (nssuint_t)len;
        off += ROUND_UP(len, sizeof (nssuint_t));
        *poff = off;
        return (NSS_SUCCESS);
}

/*
 * Switch packed and _nsc (switch->nscd) interfaces
 * Return: NSS_SUCCESS (OK to proceed), NSS_ERROR, NSS_NOTFOUND
 */

nss_status_t
nss_pack(void *buffer, size_t bufsize, nss_db_root_t *rootp __unused,
    nss_db_initf_t initf, int search_fnum, void *search_args)
{
        nss_pheader_t           *pbuf = (nss_pheader_t *)buffer;
        nss_XbyY_args_t         *in = (nss_XbyY_args_t *)search_args;
        nss_db_params_t         tparam = { 0 };
        nss_status_t            ret = NSS_ERROR;
        const char              *dbn;
        size_t                  blen, len, off = 0;
        char                    *bptr;
        struct nss_groupsbymem  *gbm;

        if (pbuf == NULL || in == NULL || initf == (nss_db_initf_t)NULL) {
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }
        tparam.cleanup = NULL;
        (*initf)(&tparam);
        if ((dbn = tparam.name) == 0) {
                if (tparam.cleanup != 0)
                        (tparam.cleanup)(&tparam);
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }

        /* init buffer header */
        pbuf->pbufsiz = (nssuint_t)bufsize;
        pbuf->p_ruid = (uint32_t)getuid();
        pbuf->p_euid = (uint32_t)geteuid();
        pbuf->p_version = NSCD_HEADER_REV;
        pbuf->p_status = 0;
        pbuf->p_errno = 0;
        pbuf->p_herrno = 0;

        /* possible audituser init */
        if (strcmp(dbn, NSS_DBNAM_AUTHATTR) == 0 && in->h_errno != 0)
                pbuf->p_herrno = (uint32_t)in->h_errno;

        pbuf->libpriv = 0;

        off = sizeof (nss_pheader_t);

        /* setup getXbyY operation - database and sub function */
        pbuf->nss_dbop = (uint32_t)search_fnum;
        ret = nss_pack_dbd(buffer, bufsize, &tparam, &off);
        if (ret != NSS_SUCCESS) {
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }
        ret = NSS_ERROR;
        /* setup request key */
        pbuf->key_off = (nssuint_t)off;
        bptr = (char *)buffer + off;
        blen = bufsize - off;
        /* use key2str if provided, else call default getXbyY packer */
        if (strcmp(dbn, NSS_DBNAM_NETGROUP) == 0) {
                /* This has to run locally due to backend knowledge */
                if (search_fnum == NSS_DBOP_NETGROUP_SET) {
                        errno = 0;
                        return (NSS_TRYLOCAL);
                }
                /* use default packer for known getXbyY ops */
                ret = nss_default_key2str(bptr, blen, in, dbn,
                    search_fnum, &len);
        } else if (in->key2str == NULL ||
            (search_fnum == NSS_DBOP_GROUP_BYMEMBER &&
            strcmp(dbn, NSS_DBNAM_GROUP) == 0)) {
                /* use default packer for known getXbyY ops */
                ret = nss_default_key2str(bptr, blen, in, dbn,
                    search_fnum, &len);
        } else {
                ret = (*in->key2str)(bptr, blen, &in->key, &len);
        }
        if (tparam.cleanup != 0)
                (tparam.cleanup)(&tparam);
        if (ret != NSS_SUCCESS) {
                errno = ERANGE;                 /* actually ENOMEM */
                return (ret);
        }
        pbuf->key_len = (nssuint_t)len;
        off += ROUND_UP(len, sizeof (nssuint_t));

        pbuf->data_off = (nssuint_t)off;
        pbuf->data_len = (nssuint_t)(bufsize - off);
        /*
         * Prime data return with first result if
         * the first result is passed in
         * [_getgroupsbymember oddness]
         */
        gbm = (struct nss_groupsbymem *)search_args;
        if (search_fnum == NSS_DBOP_GROUP_BYMEMBER &&
            strcmp(dbn, NSS_DBNAM_GROUP) == 0 && gbm->numgids == 1) {
                gid_t   *gidp;
                gidp = (gid_t *)((void *)((char *)buffer + off));
                *gidp = gbm->gid_array[0];
        }

        errno = 0;                              /* just in case ... */
        return (NSS_SUCCESS);
}

/*
 * Switch packed and _nsc (switch->nscd) {set/get/end}ent interfaces
 * Return: NSS_SUCCESS (OK to proceed), NSS_ERROR, NSS_NOTFOUND
 */

nss_status_t
nss_pack_ent(void *buffer, size_t bufsize, nss_db_root_t *rootp __unused,
    nss_db_initf_t initf, nss_getent_t *contextpp)
{
        nss_pheader_t           *pbuf = (nss_pheader_t *)buffer;
        struct nss_getent_context *contextp = contextpp->ctx;
        nss_status_t            ret = NSS_ERROR;
        size_t                  blen, len = 0, off = 0;
        char                    *bptr;
        nssuint_t               *nptr;

        if (pbuf == NULL || initf == (nss_db_initf_t)NULL) {
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }

        /* init buffer header */
        pbuf->pbufsiz = (nssuint_t)bufsize;
        pbuf->p_ruid = (uint32_t)getuid();
        pbuf->p_euid = (uint32_t)geteuid();
        pbuf->p_version = NSCD_HEADER_REV;
        pbuf->p_status = 0;
        pbuf->p_errno = 0;
        pbuf->p_herrno = 0;
        pbuf->libpriv = 0;

        off = sizeof (nss_pheader_t);

        /* setup getXXXent operation - database and sub function */
        pbuf->nss_dbop = (uint32_t)0;   /* iterators have no dbop */
        ret = nss_pack_dbd(buffer, bufsize, &contextp->param, &off);
        if (ret != NSS_SUCCESS) {
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }
        ret = NSS_ERROR;
        off += ROUND_UP(len, sizeof (nssuint_t));

        pbuf->key_off = (nssuint_t)off;
        bptr = (char *)buffer + off;
        blen = bufsize - off;
        len = (size_t)(sizeof (nssuint_t) * 2);
        if (len >= blen) {
                errno = ERANGE;                 /* actually EINVAL */
                return (ret);
        }
        nptr = (nssuint_t *)((void *)bptr);
        *nptr++ = contextp->cookie;
        *nptr = contextp->seq_num;
        pbuf->key_len = (nssuint_t)len;

        off += len;
        pbuf->data_off = (nssuint_t)off;
        pbuf->data_len = (nssuint_t)(bufsize - off);
        return (NSS_SUCCESS);
}

/*
 * Unpack packed arguments buffer
 * Return: status, errnos and results from requested operation.
 *
 * NOTES: When getgroupsbymember is being processed in the NSCD backend,
 * or via the backwards compatibility interfaces then the standard
 * str2group API is used in conjunction with process_cstr.  When,
 * processing a returned buffer, in NSS2 the return results are the
 * already digested groups array.  Therefore, unpack the digested results
 * back to the return buffer.
 *
 * Note: the digested results are nssuint_t quantities.  _getgroupsbymember
 * digests int quantities.  Therefore convert.  Assume input is in nssuint_t
 * quantities.  Store in an int array... Assume gid's are <= 32 bits...
 */

nss_status_t
nss_unpack(void *buffer, size_t bufsize __unused, nss_db_root_t *rootp __unused,
    nss_db_initf_t initf __unused, int search_fnum, void *search_args)
{
        nss_pheader_t           *pbuf = (nss_pheader_t *)buffer;
        nss_XbyY_args_t         *in = (nss_XbyY_args_t *)search_args;
        nss_dbd_t               *pdbd;
        char                    *dbn;
        nss_status_t            status;
        char                    *buf;
        int                     len;
        int                     ret;
        int                     i;
        int                     fmt_type;
        gid_t                   *gidp;
        gid_t                   *gptr;
        struct nss_groupsbymem  *arg;


        if (pbuf == NULL || in == NULL)
                return (-1);
        status = pbuf->p_status;
        /* Identify odd cases */
        pdbd = (nss_dbd_t *)((void *)((char *)buffer + pbuf->dbd_off));
        dbn = (char *)pdbd + pdbd->o_name;
        fmt_type = 0; /* nss_XbyY_args_t */
        if (search_fnum == NSS_DBOP_GROUP_BYMEMBER &&
            strcmp(dbn, NSS_DBNAM_GROUP) == 0)
                fmt_type = 1; /* struct nss_groupsbymem */
        else if (search_fnum == NSS_DBOP_NETGROUP_IN &&
            strcmp(dbn, NSS_DBNAM_NETGROUP) == 0)
                fmt_type = 2; /* struct nss_innetgr_args */

        /* if error - door's switch error */
        /* extended data could contain additional information? */
        if (status != NSS_SUCCESS) {
                if (fmt_type == 0) {
                        in->h_errno = (int)pbuf->p_herrno;
                        if (pbuf->p_errno == ERANGE)
                                in->erange = 1;
                }
                return (status);
        }

        if (pbuf->data_off == 0 || pbuf->data_len == 0)
                return (NSS_NOTFOUND);

        buf = (char *)buffer + pbuf->data_off;
        len = pbuf->data_len;

        /* sidestep odd cases */
        if (fmt_type == 1) {
                arg = (struct nss_groupsbymem *)in;
                /* copy returned gid array from returned nscd buffer */
                i = len / sizeof (gid_t);
                /* not enough buffer */
                if (i > arg->maxgids) {
                        i = arg->maxgids;
                }
                arg->numgids = i;
                gidp = arg->gid_array;
                gptr = (gid_t *)((void *)buf);
                (void) memcpy(gidp, gptr, len);
                return (NSS_SUCCESS);
        }
        if (fmt_type == 2) {
                struct nss_innetgr_args *arg = (struct nss_innetgr_args *)in;

                if (pbuf->p_status == NSS_SUCCESS) {
                        arg->status = NSS_NETGR_FOUND;
                        return (NSS_SUCCESS);
                } else {
                        arg->status = NSS_NETGR_NO;
                        return (NSS_NOTFOUND);
                }
        }

        /* process the normal cases */
        /* marshall data directly into users buffer */
        ret = (*in->str2ent)(buf, len, in->buf.result, in->buf.buffer,
            in->buf.buflen);
        if (ret == NSS_STR_PARSE_ERANGE) {
                in->returnval = 0;
                in->returnlen = 0;
                in->erange    = 1;
                ret = NSS_NOTFOUND;
        } else if (ret == NSS_STR_PARSE_SUCCESS) {
                in->returnval = in->buf.result;
                in->returnlen =  len;
                ret = NSS_SUCCESS;
        }
        in->h_errno = (int)pbuf->p_herrno;
        return ((nss_status_t)ret);
}

/*
 * Unpack a returned packed {set,get,end}ent arguments buffer
 * Return: status, errnos, cookie info and results from requested operation.
 */

nss_status_t
nss_unpack_ent(void *buffer, size_t bufsize __unused,
    nss_db_root_t *rootp __unused, nss_db_initf_t initf __unused,
    nss_getent_t *contextpp, void *args)
{
        nss_pheader_t           *pbuf = (nss_pheader_t *)buffer;
        nss_XbyY_args_t         *in = (nss_XbyY_args_t *)args;
        struct nss_getent_context *contextp = contextpp->ctx;
        nssuint_t               *nptr;
        nssuint_t               cookie;
        nss_status_t            status;
        char                    *buf;
        int                     len;
        int                     ret;

        if (pbuf == NULL)
                return (-1);
        status = pbuf->p_status;
        /* if error - door's switch error */
        /* extended data could contain additional information? */
        if (status != NSS_SUCCESS)
                return (status);

        /* unpack assigned cookie from SET/GET/END request */
        if (pbuf->key_off == 0 ||
            pbuf->key_len != (sizeof (nssuint_t) * 2))
                return (NSS_NOTFOUND);

        nptr = (nssuint_t *)((void *)((char *)buffer + pbuf->key_off));
        cookie = contextp->cookie;
        if (cookie != NSCD_NEW_COOKIE && cookie != contextp->cookie_setent &&
            cookie != *nptr) {
                /*
                 * Should either be new, or the cookie returned by the last
                 * setent (i.e., this is the first getent after the setent)
                 * or a match, else error
                 */
                return (NSS_NOTFOUND);
        }
        /* save away for the next ent request */
        contextp->cookie = *nptr++;
        contextp->seq_num = *nptr;

        /* All done if no marshalling is expected {set,end}ent */
        if (args == NULL)
                return (NSS_SUCCESS);

        /* unmarshall the data */
        if (pbuf->data_off == 0 || pbuf->data_len == 0)
                return (NSS_NOTFOUND);
        buf = (char *)buffer + pbuf->data_off;

        len = pbuf->data_len;

        /* marshall data directly into users buffer */
        ret = (*in->str2ent)(buf, len, in->buf.result, in->buf.buffer,
            in->buf.buflen);
        if (ret == NSS_STR_PARSE_ERANGE) {
                in->returnval = 0;
                in->returnlen = 0;
                in->erange    = 1;
        } else if (ret == NSS_STR_PARSE_SUCCESS) {
                in->returnval = in->buf.result;
                in->returnlen =  len;
        }
        in->h_errno = (int)pbuf->p_herrno;
        return ((nss_status_t)ret);
}

/*
 * Start of _nsc_{search|setent_u|getent_u|endent_u} NSCD interposition funcs
 */

nss_status_t
_nsc_search(nss_db_root_t *rootp, nss_db_initf_t initf, int search_fnum,
    void *search_args)
{
        nss_pheader_t           *pbuf;
        void                    *doorptr = NULL;
        size_t                  bufsize = 0;
        size_t                  datasize = 0;
        nss_status_t            status;

        if (_nsc_proc_is_cache() > 0) {
                /* internal nscd call - don't use the door */
                return (NSS_TRYLOCAL);
        }

        /* standard client calls nscd code */
        if (search_args == NULL)
                return (NSS_NOTFOUND);

        /* get the door buffer  & configured size */
        bufsize = ((nss_XbyY_args_t *)search_args)->buf.buflen;
        if (_nsc_getdoorbuf(&doorptr, &bufsize) != 0)
                return (NSS_TRYLOCAL);
        if (doorptr == NULL || bufsize == 0)
                return (NSS_TRYLOCAL);

        pbuf = (nss_pheader_t *)doorptr;
        /* pack argument and request into door buffer */
        pbuf->nsc_callnumber = NSCD_SEARCH;
        /* copy relevant door request info into door buffer */
        status = nss_pack((void *)pbuf, bufsize, rootp,
            initf, search_fnum, search_args);

        /* Packing error return error results */
        if (status != NSS_SUCCESS)
                return (status);

        /* transfer packed switch request to nscd via door */
        /* data_off can be used because it is header+dbd_len+key_len */
        datasize = pbuf->data_off;
        status = _nsc_trydoorcall_ext(&doorptr, &bufsize, &datasize);

        /* If unsuccessful fallback to standard nss logic */
        if (status != NSS_SUCCESS) {
                /*
                 * check if doors reallocated the memory underneath us
                 * if they did munmap it or suffer a memory leak
                 */
                if (doorptr != (void *)pbuf) {
                        _nsc_resizedoorbuf(bufsize);
                        (void) munmap((void *)doorptr, bufsize);
                }
                return (NSS_TRYLOCAL);
        }

        /* unpack and marshall data/errors to user structure */
        /* set any error conditions */
        status = nss_unpack((void *)doorptr, bufsize, rootp, initf,
            search_fnum, search_args);
        /*
         * check if doors reallocated the memory underneath us
         * if they did munmap it or suffer a memory leak
         */
        if (doorptr != (void *)pbuf) {
                _nsc_resizedoorbuf(bufsize);
                (void) munmap((void *)doorptr, bufsize);
        }
        return (status);
}

/*
 * contact nscd for a cookie or to reset an existing cookie
 * if nscd fails (NSS_TRYLOCAL) then set cookie to -1 and
 * continue diverting to local
 */
nss_status_t
_nsc_setent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp)
{
        nss_status_t            status = NSS_TRYLOCAL;
        struct nss_getent_context *contextp = contextpp->ctx;
        nss_pheader_t           *pbuf;
        void                    *doorptr = NULL;
        size_t                  bufsize = 0;
        size_t                  datasize = 0;

        /* return if already in local mode */
        if (contextp->cookie == NSCD_LOCAL_COOKIE)
                return (NSS_TRYLOCAL);

        if (_nsc_proc_is_cache() > 0) {
                /* internal nscd call - don't try to use the door */
                contextp->cookie = NSCD_LOCAL_COOKIE;
                return (NSS_TRYLOCAL);
        }

        /* get the door buffer & configured size */
        if (_nsc_getdoorbuf(&doorptr, &bufsize) != 0) {
                contextp->cookie = NSCD_LOCAL_COOKIE;
                return (NSS_TRYLOCAL);
        }
        if (doorptr == NULL || bufsize == 0) {
                contextp->cookie = NSCD_LOCAL_COOKIE;
                return (NSS_TRYLOCAL);
        }

        pbuf = (nss_pheader_t *)doorptr;
        pbuf->nsc_callnumber = NSCD_SETENT;

        contextp->param.cleanup = NULL;
        (*initf)(&contextp->param);
        if (contextp->param.name == 0) {
                if (contextp->param.cleanup != 0)
                        (contextp->param.cleanup)(&contextp->param);
                errno = ERANGE;                 /* actually EINVAL */
                return (NSS_ERROR);
        }

        /* pack relevant setent request info into door buffer */
        status = nss_pack_ent((void *)pbuf, bufsize, rootp, initf, contextpp);
        if (status != NSS_SUCCESS)
                return (status);

        /* transfer packed switch request to nscd via door */
        /* data_off can be used because it is header+dbd_len+key_len */
        datasize = pbuf->data_off;
        status = _nsc_trydoorcall_ext(&doorptr, &bufsize, &datasize);

        /* If fallback to standard nss logic (door failure) if possible */
        if (status != NSS_SUCCESS) {
                if (contextp->cookie == NSCD_NEW_COOKIE) {
                        contextp->cookie = NSCD_LOCAL_COOKIE;
                        return (NSS_TRYLOCAL);
                }
                return (NSS_UNAVAIL);
        }
        /* unpack returned cookie stash it away */
        status = nss_unpack_ent((void *)doorptr, bufsize, rootp,
            initf, contextpp, NULL);
        /* save the setent cookie for later use */
        contextp->cookie_setent = contextp->cookie;
        /*
         * check if doors reallocated the memory underneath us
         * if they did munmap it or suffer a memory leak
         */
        if (doorptr != (void *)pbuf) {
                _nsc_resizedoorbuf(bufsize);
                (void) munmap((void *)doorptr, bufsize);
        }
        return (status);
}

nss_status_t
_nsc_getent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp, void *args)
{
        nss_status_t            status = NSS_TRYLOCAL;
        struct nss_getent_context *contextp = contextpp->ctx;
        nss_pheader_t           *pbuf;
        void                    *doorptr = NULL;
        size_t                  bufsize = 0;
        size_t                  datasize = 0;

        /* return if already in local mode */
        if (contextp->cookie == NSCD_LOCAL_COOKIE)
                return (NSS_TRYLOCAL);

        /* _nsc_setent_u already checked for nscd local case ... proceed */
        if (args == NULL)
                return (NSS_NOTFOUND);

        /* get the door buffer  & configured size */
        bufsize = ((nss_XbyY_args_t *)args)->buf.buflen;
        if (_nsc_getdoorbuf(&doorptr, &bufsize) != 0)
                return (NSS_UNAVAIL);
        if (doorptr == NULL || bufsize == 0)
                return (NSS_UNAVAIL);

        pbuf = (nss_pheader_t *)doorptr;
        pbuf->nsc_callnumber = NSCD_GETENT;

        /* pack relevant setent request info into door buffer */
        status = nss_pack_ent((void *)pbuf, bufsize, rootp, initf, contextpp);
        if (status != NSS_SUCCESS)
                return (status);

        /* transfer packed switch request to nscd via door */
        /* data_off can be used because it is header+dbd_len+key_len */
        datasize = pbuf->data_off;
        status = _nsc_trydoorcall_ext(&doorptr, &bufsize, &datasize);

        /* If fallback to standard nss logic (door failure) if possible */
        if (status != NSS_SUCCESS) {
                if (status == NSS_TRYLOCAL ||
                    contextp->cookie == NSCD_NEW_COOKIE) {
                        contextp->cookie = NSCD_LOCAL_COOKIE;

                        /* init the local cookie */
                        nss_setent_u(rootp, initf, contextpp);
                        if (contextpp->ctx == 0)
                                return (NSS_UNAVAIL);
                        return (NSS_TRYLOCAL);
                }
                return (NSS_UNAVAIL);
        }
        /* check error, unpack and process results */
        status = nss_unpack_ent((void *)doorptr, bufsize, rootp,
            initf, contextpp, args);
        /*
         * check if doors reallocated the memory underneath us
         * if they did munmap it or suffer a memory leak
         */
        if (doorptr != (void *)pbuf) {
                _nsc_resizedoorbuf(bufsize);
                (void) munmap((void *)doorptr, bufsize);
        }
        return (status);
}

nss_status_t
_nsc_endent_u(nss_db_root_t *rootp, nss_db_initf_t initf,
    nss_getent_t *contextpp)
{
        nss_status_t            status = NSS_TRYLOCAL;
        struct nss_getent_context *contextp = contextpp->ctx;
        nss_pheader_t           *pbuf;
        void                    *doorptr = NULL;
        size_t                  bufsize = 0;
        size_t                  datasize = 0;

        /* return if already in local mode */
        if (contextp->cookie == NSCD_LOCAL_COOKIE)
                return (NSS_TRYLOCAL);

        /* _nsc_setent_u already checked for nscd local case ... proceed */

        /* get the door buffer  & configured size */
        if (_nsc_getdoorbuf(&doorptr, &bufsize) != 0)
                return (NSS_UNAVAIL);
        if (doorptr == NULL || bufsize == 0)
                return (NSS_UNAVAIL);

        /* pack up a NSCD_ENDGET request passing in the cookie */
        pbuf = (nss_pheader_t *)doorptr;
        pbuf->nsc_callnumber = NSCD_ENDENT;

        /* pack relevant setent request info into door buffer */
        status = nss_pack_ent((void *)pbuf, bufsize, rootp, initf, contextpp);
        if (status != NSS_SUCCESS)
                return (status);

        /* transfer packed switch request to nscd via door */
        /* data_off can be used because it is header+dbd_len+key_len */
        datasize = pbuf->data_off;
        (void) _nsc_trydoorcall_ext(&doorptr, &bufsize, &datasize);

        /* error codes & unpacking ret values don't matter.  We're done */

        /*
         * check if doors reallocated the memory underneath us
         * if they did munmap it or suffer a memory leak
         */
        if (doorptr != (void *)pbuf) {
                _nsc_resizedoorbuf(bufsize);
                (void) munmap((void *)doorptr, bufsize);
        }

        /* clean up initf setup */
        if (contextp->param.cleanup != 0)
                (contextp->param.cleanup)(&contextp->param);
        contextp->param.cleanup = NULL;

        /* clear cookie */
        contextp->cookie = NSCD_NEW_COOKIE;
        return (NSS_SUCCESS);
}

/*
 * Internal private API to return default suggested buffer sizes
 * for nsswitch API requests.
 */

size_t
_nss_get_bufsizes(int arg)
{
        switch (arg) {
        case _SC_GETGR_R_SIZE_MAX:
                return (__nss_buflen_group);
        }
        return (__nss_buflen_default);
}

void *
_nss_XbyY_fini(nss_XbyY_args_t *args)
{
        if ((args->returnval == NULL) && (args->erange != 0))
                errno = ERANGE;
        return (args->returnval);
}