/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2021 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


/*
    Virtual Attributes

    This file implements the "virtual attribute API", which is the interface
    by which any joe servercode gets the values of attributes associated with an
    entry which are _not_ stored with the entry. Attributes which _are_ stored
    with the entry will also be returned by this interface, unless the caller
    specifies the SLAPI_REALATTRS_ONLY flag. This means that the casual caller
    looking for attribute values should probably call the virtual attribute interface
    rather than the regular Slapi_Entry_Attr_Find... and so on calls.
    This interface is different from that one in that the data returned is
    a copy, to be freed by the caller. Details on how to free the returned data is
    given individually for each function.

    The thing is implemented with a service provider model. The code in here
    does NOT know how to generate computed attr values etc, it just calls the
    registered providers of such information. It also takes care of any crusty
    special cases. Everything above the line here is crispy clean.

    Implicit in this interface is the assumption that the value of an attribute
    can only depend on the entry and some independent stored state. For example,
    the value can't depend upon who is asking, since we don't pass that information
    over the interface.

  More design: we'd like to have the regular result returning code in result.c call this
  interface. However, as it stands, his would incur a malloc, a copy and a free for every
  value. Too expensive. So, it would be good to modify the interface such that when we
  retrieve values from inside the entry, we just fetch a pointer like before. When we
  retrieve values which are generated, we get copies of the data (or can we get pointers
  there too ?? --- no).

  One way to achieve this: allow the caller to say that they perfer to receive pointers
  and not copies. They are then informed by the function whether they did receive pointers
  or copies. They then call the free function only when needed. The implicit lifetime of
  the returned pointers is the lifetime of the entry object passed into the function.
  Nasty, but one has to do these things in the name of the god performance.

  DBDB: remember to rename the structures mark complained weren't compliant with the slapi naming scheme.

*/

#include "slap.h"

#include "vattr_spi.h"
#include "statechange.h"

#ifdef SOURCEFILE
#undef SOURCEFILE
#endif
#define SOURCEFILE "vattr.c"
static char *sourcefile = SOURCEFILE;

/* stolen from roles_cache.h, must remain in sync */
#define NSROLEATTR "nsRole"
static Slapi_Eq_Context vattr_check_ctx = {0};

/* Define only for module test code */
/* #define VATTR_TEST_CODE */

/* Loop context structure */
struct _vattr_context
{
    Slapi_PBlock *pb;
    unsigned int vattr_context_loop_count;
    unsigned int error_displayed;
};
#define VATTR_LOOP_COUNT_MAX 50

typedef vattr_sp_handle vattr_sp_handle_list;

/* Local prototypes */
static int vattr_map_create(void);
static void vattr_map_destroy(void);
int vattr_map_sp_insert(char *type_to_add, vattr_sp_handle *sp, void *hint);
vattr_sp_handle_list *vattr_map_sp_getlist(char *type_to_find);
vattr_sp_handle_list *vattr_map_namespace_sp_getlist(Slapi_DN *dn, const char *type_to_find);
vattr_sp_handle_list *vattr_map_sp_get_complete_list(void);
vattr_sp_handle *vattr_map_sp_first(vattr_sp_handle_list *list, void **hint);
vattr_sp_handle *vattr_map_sp_next(vattr_sp_handle_list *list, void **hint);
vattr_sp_handle *vattr_list_sp_first(vattr_sp_handle_list *list);
vattr_sp_handle *vattr_list_sp_next(vattr_sp_handle_list *list);
int vattr_call_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char *type, Slapi_ValueSet **results, int *type_name_disposition, char **actual_type_name, int flags, int *buffer_flags, void *hint);
int vattr_call_sp_get_batch_values(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, char **type, Slapi_ValueSet ***results, int **type_name_disposition, char ***actual_type_name, int flags, int *buffer_flags, void **hint);
int vattr_call_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get, const char *type, Slapi_Value *test_this, int *result, int flags, void *hint);
int vattr_call_sp_get_types(vattr_sp_handle *handle, Slapi_Entry *e, vattr_type_list_context *type_context, int flags);
void schema_changed_callback(Slapi_Entry *e, char *dn, int modtype, Slapi_PBlock *pb, void *caller_data);
int slapi_vattrspi_register_internal(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_get_ex_fn_type get_ex_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options);

#ifdef VATTR_TEST_CODE
int vattr_basic_sp_init();
#endif

void **statechange_api;

struct _vattr_map
{
    Slapi_RWLock *lock;
    PLHashTable *hashtable; /* Hash table */
};
typedef struct _vattr_map vattr_map;

static vattr_map *the_map = NULL;

/* Housekeeping Functions, called by server startup/shutdown code */

/* Called on server startup, init all structures etc */
void
vattr_init()
{
    statechange_api = 0;
    vattr_map_create();

#ifdef VATTR_TEST_CODE
    vattr_basic_sp_init();
#endif
}
/* Called on server shutdown, free all structures, inform service providers that we're going down etc */
void
vattr_cleanup()
{
    /* We need to free and remove anything that was inserted first */
    vattr_map_destroy();
    slapi_eq_cancel_rel(vattr_check_ctx);
}

static void
vattr_check_thread(void *arg)
{
    Slapi_Backend *be = NULL;
    char *cookie = NULL;
    Slapi_DN *base_sdn = NULL;
    Slapi_PBlock *search_pb = NULL;
    Slapi_Entry **entries = NULL;
    int32_t rc;
    int32_t check_suffix; /* used to skip suffixes in ignored_backend */
    PRBool exist_vattr_definition = PR_FALSE;
    char *ignored_backend[5] = {"cn=config", "cn=schema", "cn=monitor", "cn=changelog", NULL}; /* suffixes to ignore */
    char *suffix;
    int ignore_vattrs;

    ignore_vattrs = config_get_ignore_vattrs();

    if (!ignore_vattrs) {
        /* Nothing to do more, we are already evaluating virtual attribute */
        return;
    }

    search_pb = slapi_pblock_new();
    be = slapi_get_first_backend(&cookie);
    while (be && !exist_vattr_definition && !slapi_is_shutting_down()) {
        base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0);
        suffix = (char *) slapi_sdn_get_dn(base_sdn);

        if (suffix) {
            /* First check that we need to check that suffix */
            check_suffix = 1;
            for (size_t i = 0; ignored_backend[i]; i++) {
                if (strcasecmp(suffix, ignored_backend[i]) == 0) {
                    check_suffix = 0;
                    break;
                }
            }

            /* search for a role or cos definition */
            if (check_suffix) {
                slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
                        LDAP_SCOPE_SUBTREE, "(&(objectclass=ldapsubentry)(|(objectclass=nsRoleDefinition)(objectclass=cosSuperDefinition)))",
                        NULL, 0, NULL, NULL, (void *) plugin_get_default_component_id(), 0);
                slapi_search_internal_pb(search_pb);
                slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);

                if (rc == LDAP_SUCCESS) {
                    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
                    if (entries && entries[0]) {
                        /* it exists at least a cos or role definition */
                        exist_vattr_definition = PR_TRUE;
                        slapi_log_err(SLAPI_LOG_INFO,
                                "vattr_check_thread",
                                "Found a role/cos definition in %s\n", slapi_entry_get_dn(entries[0]));
                    } else {
                        slapi_log_err(SLAPI_LOG_INFO,
                                "vattr_check_thread",
                                "No role/cos definition in %s\n", slapi_sdn_get_dn(base_sdn));
                    }
                }
                slapi_free_search_results_internal(search_pb);
            } /* check_suffix */
        } /* suffix */
        be = (backend *) slapi_get_next_backend(cookie);
    }
    slapi_pblock_destroy(search_pb);
    slapi_ch_free_string(&cookie);

    /* Now if a virtual attribute is defined, then CONFIG_IGNORE_VATTRS -> off */
    if (exist_vattr_definition) {
        char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE];
        errorbuf[0] = '\0';
        config_set_ignore_vattrs(CONFIG_IGNORE_VATTRS, "off", errorbuf, 1);
        slapi_log_err(SLAPI_LOG_INFO,
                      "vattr_check_thread",
                      "Because of virtual attribute definition, %s was set to 'off'\n", CONFIG_IGNORE_VATTRS);
    }
}
static void
vattr_check_schedule_once(time_t when __attribute__((unused)), void *arg)
{
    if (PR_CreateThread(PR_USER_THREAD,
                        vattr_check_thread,
                        (void *) arg,
                        PR_PRIORITY_NORMAL,
                        PR_GLOBAL_THREAD,
                        PR_UNJOINABLE_THREAD,
                        SLAPD_DEFAULT_THREAD_STACKSIZE) == NULL) {
        slapi_log_err(SLAPI_LOG_ERR,
                      "vattr_check_schedule_once",
                      "Fails to check if %s needs to be toggled to FALSE\n", CONFIG_IGNORE_VATTRS);
    }
}
#define VATTR_CHECK_DELAY 3
void
vattr_check()
{
    /* Schedule running a callback that will create a thread
     * but make sure it is called a first thing when event loop is created */
    time_t now;

    now = slapi_current_rel_time_t();
    vattr_check_ctx = slapi_eq_once_rel(vattr_check_schedule_once, NULL, now + VATTR_CHECK_DELAY);
}

/* The public interface functions start here */

/* Function which returns the value(s) of an attribute, given an entry and the attribute type name */
/* Return 0 if OK, ?? if attr doesn't exist, ?? if some error condition ?? if result doesn't need to be free'ed */

int
slapi_vattr_values_get(/* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet **results, int *type_name_disposition, char **actual_type_name, int flags, int *buffer_flags)
{
    vattr_context *c = NULL;
    return slapi_vattr_values_get_sp(c, e, type, results, type_name_disposition, actual_type_name, flags, buffer_flags);
}

int
slapi_vattr_values_get_ex(/* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet ***results, int **type_name_disposition, char ***actual_type_name, int flags, int *buffer_flags, int *subtype_count)
{
    vattr_context *c = NULL;
    return slapi_vattr_values_get_sp_ex(c, e, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, subtype_count);
}

int
slapi_vattr_namespace_values_get(/* Entry we're interested in */ Slapi_Entry *e, /* backend namespace dn*/ Slapi_DN *namespace_dn, /* attr type name */ char *type, /* pointer to result set */ Slapi_ValueSet ***results, int **type_name_disposition, char ***actual_type_name, int flags, int *buffer_flags, int *subtype_count)
{
    vattr_context *c = NULL;
    return slapi_vattr_namespace_values_get_sp(c, e, namespace_dn, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, subtype_count);
}


/*
 * If pointers into the entry were requested then we might have
 * a stashed pointer to the entry values, otherwise will be null.
 */
Slapi_ValueSet *
vattr_typethang_get_values(vattr_type_thang *t)
{
    return t->type_values;
}

/*
 * This can be much faster than using slapi_vattr_values_get()
 * when you have a vattr_type_thang list returned from slapi_vattr_list_types().
 *
 * We call the vattr SPs to get values for an attribute type in the list only
 * if the results field for that attribute type is null.
 * If the type list comes from slapi_vattr_list_types() then the value is null
 * only for attributes for which an SP wishes to provide a value, so fo the other
 * ones don't bother calling the SPs again--just use the real value we picked up
 * from the entry.
 *
 */
int
slapi_vattr_values_type_thang_get(
    Slapi_Entry *e,
    vattr_type_thang *type_thang,
    Slapi_ValueSet **results,
    int *type_name_disposition,
    char **actual_type_name,
    int flags,
    int *buffer_flags)
{
    int rc = 0;
    char *type = NULL;

    type = vattr_typethang_get_name(type_thang);
    *results = vattr_typethang_get_values(type_thang);

    if (*results != NULL) {
        /* we already have a pointer directly into the entry */
        *actual_type_name = type;
        *type_name_disposition =
            SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
        *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS;
    } else {
        /* fall back to usual implementation */
        rc = slapi_vattr_values_get(e, type, results,
                                    type_name_disposition,
                                    actual_type_name,
                                    flags, buffer_flags);
    }

    return rc;
}

static void
vattr_helper_get_entry_conts(Slapi_Entry *e, char *type, vattr_get_thang *my_get)
{
    Slapi_Attr *a = NULL;
    void *dummy = 0;

    a = attrlist_find_ex(e->e_attrs, type, &(my_get->get_name_disposition), &(my_get->get_type_name), &dummy);
    if (a) {
        my_get->get_present = 1;
        my_get->get_present_values = &(a->a_present_values);
        my_get->get_attr = a;
    }
}

static int
vattr_helper_get_entry_conts_with_subtypes(Slapi_Entry *e, const char *type, vattr_get_thang **my_get)
{
    Slapi_Attr *a = NULL;
    void *hint = 0;
    int counter = 0;
    int attr_count = attrlist_count_subtypes(e->e_attrs, type);

    if (attr_count > 0) {
        *my_get = (vattr_get_thang *)slapi_ch_calloc(attr_count, sizeof(vattr_get_thang));

        /* pick up attributes with sub-types and slip into the get_thang list */
        for (counter = 0; counter < attr_count; counter++) {
            a = attrlist_find_ex(e->e_attrs, type, &((*my_get)[counter].get_name_disposition), &((*my_get)[counter].get_type_name), &hint);
            if (a) {
                (*my_get)[counter].get_present = 1;
                (*my_get)[counter].get_present_values = &(a->a_present_values);
                (*my_get)[counter].get_attr = a;
            }
        }
    }

    return attr_count;
}

static int
vattr_helper_get_entry_conts_no_subtypes(Slapi_Entry *e, const char *type, vattr_get_thang **my_get)
{
    int attr_count = 0;
    Slapi_Attr *a = attrlist_find(e->e_attrs, type);

    if (a) {
        attr_count = 1;
        *my_get = (vattr_get_thang *)slapi_ch_calloc(1, sizeof(vattr_get_thang));
        (*my_get)[0].get_present = 1;
        (*my_get)[0].get_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
        (*my_get)[0].get_type_name = a->a_type;
        (*my_get)[0].get_present_values = &(a->a_present_values);
        (*my_get)[0].get_attr = a;
    }

    return attr_count;
}

static int
vattr_helper_get_entry_conts_ex(Slapi_Entry *e, const char *type, vattr_get_thang **my_get, int suppress_subtypes)
{
    int rc;

    if (suppress_subtypes) {
        rc = vattr_helper_get_entry_conts_no_subtypes(e, type, my_get);
    } else {
        rc = vattr_helper_get_entry_conts_with_subtypes(e, type, my_get);
    }

    return rc;
}


vattr_context *
vattr_context_new(Slapi_PBlock *pb)
{
    void *pb_vattr_context = NULL;
    if (pb != NULL) {
        pb_vattr_context = slapi_pblock_get_vattr_context(pb);
    }
    vattr_context *c = NULL;
    if (pb_vattr_context) {
        c = (vattr_context *)pb_vattr_context;
    } else {
        c = (vattr_context *)slapi_ch_calloc(1, sizeof(vattr_context));
    }
    /* The payload is zero, which is what we want */
    if (c) {
        c->pb = pb;
    }
    if (pb && c != (vattr_context *)pb_vattr_context) {
        slapi_pblock_set_vattr_context(pb, (void *)c);
    }

    return c;
}

static int
vattr_context_check(vattr_context *c)
{
    /* Observe the loop count and see if it's higher than is allowed */
    if (c->vattr_context_loop_count > VATTR_LOOP_COUNT_MAX) {
        return SLAPI_VIRTUALATTRS_LOOP_DETECTED;
    } else
        return 0;
}

static void
vattr_context_mark(vattr_context *c)
{
    if (c) {
        c->vattr_context_loop_count += 1;
    }
}

static int
vattr_context_unmark(vattr_context *c)
{
    if (c) {
        return (c->vattr_context_loop_count -= 1);
    } else {
        return 0;
    }
}

/* modify the context structure on exit from a vattr sp function */
static void
vattr_context_ungrok(vattr_context **c)
{
    /* Decrement the loop count */
    if (0 == vattr_context_unmark(*c)) {
        /* If necessary, delete the structure */
        if ((*c)->pb) {
            slapi_pblock_set_vattr_context((*c)->pb, NULL);
        }
        slapi_ch_free((void **)c);
    }
}

static int
vattr_context_grok_pb(Slapi_PBlock *pb, vattr_context **c)
{
    int rc = -1;
    if (NULL == c) {
        return rc;
    }
    *c = vattr_context_new(pb);
    if (NULL == *c) {
        return ENOMEM;
    }
    rc = vattr_context_check(*c);
    vattr_context_mark(*c); /* increment loop count */
    return rc;
}

/* Check and mess with the context structure on entry to a vattr sp function */
static int
vattr_context_grok(vattr_context **c)
{
    int rc = 0;
    /* First check that we've not got into an infinite loop.
       We do so by means of the vattr_context structure.
     */

    /* Do we have a context at all ?? */
    if (NULL == *c) {
        /* No, so let's make one */
        *c = vattr_context_new(NULL);
        if (NULL == *c) {
            return ENOMEM;
        }
    } else {
        /* Yes, so let's check its contents */
        rc = vattr_context_check(*c);
    }
    /* mark the context as having been used once */
    vattr_context_mark(*c);
    return rc;
}


/* keep track of error messages so we don't spam the error log */
static void
vattr_context_set_loop_msg_displayed(vattr_context **c)
{
    if (c && *c){
        (*c)->error_displayed = 1;
    }
}

static int
vattr_context_is_loop_msg_displayed(vattr_context **c)
{
    if (c && *c){
        return (*c)->error_displayed;
    } else {
        return 0;
    }
}

/*
 * vattr_test_filter:
 *
 * . tests an ava, presence or substring filter against e.
 * . group these filter types together to avoid having to duplicate the
 * . vattr specific code in three seperate routines.
 * . handles virtual attrs in the filter.
 * . does update the vattrcache if a call to this calculates vattrs
 *
 * returns: 0    filter matched
 *            -1    filter did not match
 *            >0    an ldap error code
 *
*/
int
vattr_test_filter(Slapi_PBlock *pb,
                  /* Entry we're interested in */ Slapi_Entry *e,
                  Slapi_Filter *f,
                  filter_type_t filter_type,
                  char *type)
{
    int rc = -1;
    int sp_bit = 0; /* Set if an SP supplied an answer */
    vattr_sp_handle_list *list = NULL;
    Slapi_DN *sdn;
    Slapi_Backend *be;
    Slapi_DN *namespace_dn;

    /* get the namespace this entry belongs to */
    sdn = slapi_entry_get_sdn(e);
    be = slapi_be_select(sdn);
    namespace_dn = (Slapi_DN *)slapi_be_getsuffix(be, 0);

    /* Look for attribute in the map */

    if (namespace_dn) {
        list = vattr_map_namespace_sp_getlist(namespace_dn, type);
    } else {
        list = vattr_map_namespace_sp_getlist(NULL, type);
    }

    if (list) {
        vattr_sp_handle *current_handle = NULL;
        /* first lets consult the cache to save work */
        int cache_status;

        cache_status = slapi_entry_vattrcache_findAndTest(e, type,
                                                          f,
                                                          filter_type,
                                                          &rc);
        switch (cache_status) {
        case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */
        {
            sp_bit = 1;
            break;
        }

        case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */
            break;                              /* look in entry */

        case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */
        default:                             /* any other result, resolve */
        {
            int flags = SLAPI_VIRTUALATTRS_REQUEST_POINTERS;
            void *hint = NULL;
            Slapi_ValueSet **results = NULL; /* pointer to result set */
            int *type_name_disposition;
            char **actual_type_name;
            int buffer_flags;
            vattr_get_thang my_get = {0};
            /* bit cacky, but need to make a null terminated lists for now
                 * for the (unimplemented and so fake) batch attribute request
                 */
            char *types[2];
            void *hint_list[2];
            vattr_context *ctx;
            vattr_context_grok_pb(pb, &ctx); /* get or new context */

            types[0] = type;
            types[1] = 0;
            hint_list[1] = 0;

            for (current_handle = vattr_map_sp_first(list, &hint); current_handle; current_handle = vattr_map_sp_next(current_handle, &hint)) {
                hint_list[0] = hint;

                rc = vattr_call_sp_get_batch_values(current_handle, ctx, e,
                                                    &my_get, types, &results, &type_name_disposition,
                                                    &actual_type_name, flags, &buffer_flags, hint_list);

                if (0 == rc) {
                    sp_bit = 1;
                    break;
                }
            }
            vattr_context_ungrok(&ctx);

            if (!sp_bit) {
                /*
                     * No vattr sp supplied an answer so will look in the
                     * entry itself.
                     * but first lets cache the no result
                    */
                slapi_entry_vattrcache_merge_sv(e, type, NULL, buffer_flags);
            } else {
                /*
                     * A vattr sp supplied an answer.
                     * so turn the value into a Slapi_Attr, pass
                     *  to the syntax plugin for comparison.
                     */

                if (filter_type == FILTER_TYPE_AVA ||
                    filter_type == FILTER_TYPE_SUBSTRING) {
                    Slapi_Attr *a = NULL;
                    int i = 0;
                    /* may need this when we have a passin interface for set_valueset */
                    /*Slapi_ValueSet null_valueset = {0};*/

                    rc = -1;

                    if (results && actual_type_name && type_name_disposition) {
                        while (results[i] && rc) {
                            a = slapi_attr_new();
                            slapi_attr_init(a, type);
                            /* a now contains a *copy* of results */
                            slapi_attr_set_valueset(a, results[i]);

                            if (filter_type == FILTER_TYPE_AVA) {
                                rc = plugin_call_syntax_filter_ava(a,
                                                                   f->f_choice, &f->f_ava);
                            } else if (filter_type == FILTER_TYPE_SUBSTRING) {
                                /* covscan false positive: plugin_call_syntax_filter_sub does not free pb->pb_op */
                                /* coverity[deref_arg] */
                                /* coverity[double_free] */
                                rc = plugin_call_syntax_filter_sub(pb, a,
                                                                   &f->f_sub);
                            }

                            /*
                                 * Cache stuff: dups results
                                */
                            slapi_entry_vattrcache_merge_sv(e, actual_type_name[i],
                                                            results[i], buffer_flags);
                            /*
                                 * Free stuff, just in case we did not
                                 * get pointers.
                                */
                            slapi_vattr_values_free(&(results[i]),
                                                    &(actual_type_name[i]),
                                                    buffer_flags);
                            /* may need this when we support a passin set_valueset */
                            /*slapi_attr_set_valueset( a, &null_valueset);*/
                            /* since a contains a copy of results, we must free it */
                            slapi_attr_free(&a);
                            i++;
                        }
                    }

                    slapi_ch_free((void **)&results);
                    slapi_ch_free((void **)&actual_type_name);
                    slapi_ch_free((void **)&type_name_disposition);

                } else if (filter_type == FILTER_TYPE_PRES) {
                    /*
                         * Cache stuff: dups results
                        */
                    int i = 0;

                    while (results[i]) {
                        slapi_entry_vattrcache_merge_sv(e, actual_type_name[i],
                                                        results[i], buffer_flags);
                        /*
                             * Free stuff, just in case we did not
                             * get pointers.
                            */
                        slapi_vattr_values_free(&results[i],
                                                &actual_type_name[i],
                                                buffer_flags);
                    }
                    slapi_ch_free((void **)&results);
                    slapi_ch_free((void **)&actual_type_name);
                    slapi_ch_free((void **)&type_name_disposition);
                }
            }
            break;
        }
        } /* switch */
    }
    /* If no SP supplied the answer, take it from the entry */
    if (rc <= 1 && !sp_bit) /* if LDAP ERROR is set, skip further testing */
    {
        int acl_test_done;

        if (filter_type == FILTER_TYPE_AVA) {

            rc = test_ava_filter(NULL /* pb not needed */,
                                 e, e->e_attrs, &f->f_ava,
                                 f->f_choice,
                                 0 /* no access check */,
                                 0 /* do test filter */,
                                 &acl_test_done);

        } else if (filter_type == FILTER_TYPE_SUBSTRING) {

            rc = test_substring_filter(pb, e, f, 0 /* no access check */,
                                       0 /* do test filter */, &acl_test_done);

        } else if (filter_type == FILTER_TYPE_PRES) {

            rc = test_presence_filter(NULL, e, f->f_type,
                                      0 /* no access check */,
                                      0 /* do test filter */,
                                      &acl_test_done);
        }
    }
    return rc;
}
/*
 * deprecated in favour of slapi_vattr_values_get_sp_ex() which
 * returns subtypes too.
*/
SLAPI_DEPRECATED int
slapi_vattr_values_get_sp(vattr_context *c,
                          /* Entry we're interested in */ Slapi_Entry *e,
                          /* attr type name */ char *type,
                          /* pointer to result set */ Slapi_ValueSet **results,
                          int *type_name_disposition,
                          char **actual_type_name,
                          int flags,
                          int *buffer_flags)
{
    PRBool use_local_ctx = PR_FALSE;
    Slapi_PBlock *local_pb = NULL;
    vattr_context *ctx = NULL;
    int rc = 0;
    int sp_bit = 0; /* Set if an SP supplied an answer */
    vattr_sp_handle_list *list = NULL;

    vattr_get_thang my_get = {0};

    if (c != NULL) {
        rc = vattr_context_grok(&c);
        if (0 != rc) {
            if (!vattr_context_is_loop_msg_displayed(&c)) {
                /* Print a handy error log message */
                slapi_log_err(SLAPI_LOG_ERR,
                              "slapi_vattr_values_get_sp", "Detected virtual attribute loop in get on entry %s, attribute %s\n",
                              slapi_entry_get_dn_const(e), type);
                vattr_context_set_loop_msg_displayed(&c);
            }
            return rc;
        }
        ctx = c;
    } else {
        use_local_ctx = PR_TRUE;
        local_pb = slapi_pblock_new();
        ctx = vattr_context_new(local_pb);
        ctx->vattr_context_loop_count = 1;
        ctx->error_displayed = 0;
    }

    /* For attributes which are in the entry, we just need to get to the Slapi_Attr structure and yank out the slapi_value_set
     structure. We either return a pointer directly to it, or we copy it, depending upon whether the caller asked us to try to
     avoid copying.
  */

    /* First grok the entry, and remember what we saw. This call does no more than walk down the entry attribute list, do some string compares and copy pointers. */
    vattr_helper_get_entry_conts(e, type, &my_get);
    /* Having done that, we now consult the attribute map to find service providers who are interested */
    /* Look for attribute in the map */
    if (!(flags & SLAPI_REALATTRS_ONLY)) {
        list = vattr_map_sp_getlist(type);
        if (list) {
            vattr_sp_handle *current_handle = NULL;
            void *hint = NULL;
            /* first lets consult the cache to save work */
            int cache_status;

            cache_status =
                slapi_entry_vattrcache_find_values_and_type(e, type,
                                                            results,
                                                            actual_type_name);
            switch (cache_status) {
            case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */
            {
                sp_bit = 1;

                /* Complete analysis of type matching */
                if (0 == slapi_attr_type_cmp(type, *actual_type_name, SLAPI_TYPE_CMP_EXACT)) {
                    *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
                } else {
                    *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE;
                }

                break;
            }

            case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */
                break;                              /* look in entry */

            case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */
            default:                             /* any other result, resolve */
            {
                for (current_handle = vattr_map_sp_first(list, &hint); current_handle; current_handle = vattr_map_sp_next(current_handle, &hint)) {
                    rc = vattr_call_sp_get_value(current_handle, ctx, e, &my_get, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, hint);
                    if (0 == rc) {
                        sp_bit = 1;
                        break;
                    }
                }

                if (!sp_bit) {
                    /* clean up, we have failed and must now examine the
             * entry itself
             * But first lets cache the no result
             * Creates the type (if necessary).
            */
                    slapi_entry_vattrcache_merge_sv(e, type, NULL, *buffer_flags);

                } else {
                    /*
             * we need to cache the virtual attribute
             * creates the type (if necessary) and dups
             * results.
            */
                    slapi_entry_vattrcache_merge_sv(e, *actual_type_name,
                                                    *results, *buffer_flags);
                }

                break;
            }
            }
        }
    }
    /* If no SP supplied the answer, take it from the entry */
    if (!sp_bit && !(flags & SLAPI_VIRTUALATTRS_ONLY)) {
        rc = 0; /* reset return code (cause an sp must have failed) */
        *type_name_disposition = my_get.get_name_disposition;

        if (my_get.get_present) {
            if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) {
                *results = my_get.get_present_values;
                *actual_type_name = my_get.get_type_name;
            } else {
                *results = valueset_dup(my_get.get_present_values);
                if (NULL == *results) {
                    rc = ENOMEM;
                } else {
                    *actual_type_name = slapi_ch_strdup(my_get.get_type_name);
                    if (NULL == *actual_type_name) {
                        rc = ENOMEM;
                    }
                }
            }
            if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) {
                *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS;
            } else {
                *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
            }
        } else {
            rc = SLAPI_VIRTUALATTRS_NOT_FOUND;
        }
    }
    if (use_local_ctx) {
        /* slapi_pblock_destroy cleans up pb_vattr_context, as well */
        slapi_pblock_destroy(local_pb);
    } else {
        vattr_context_ungrok(&c);
    }
    /* covscan false positive: ctx is stored in pblock */
    /* coverity[leaked_storage] */
    return rc;
}

/*
 *
 * returns 0: (no_error && type found ) in which case:
 *              results: contains the current values for type and
 *                        all it's subtypes in e
 *              type_name_disposition: how each type was matched
 *                      SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS
 *                      SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE
 *              actual_type_name: type name as found
 *              flags: bit mask of options:
 *                      SLAPI_REALATTRS_ONLY
 *                      SLAPI_VIRTUALATTRS_ONLY
 *                      SLAPI_VIRTUALATTRS_REQUEST_POINTERS
 *                      SLAPI_VIRTUALATTRS_LIST_OPERATIONAL_ATTRS
 *              buffer_flags: bit mask to be used as input flags for
 *                              slapi_values_free()
 *                  SLAPI_VIRTUALATTRS_RETURNED_POINTERS
 *                  SLAPI_VIRTUALATTRS_RETURNED_COPIES
 *                  SLAPI_VIRTUALATTRS_REALATTRS_ONLY
 *                item_count: number of subtypes matched
 * otherwise:
 *          SLAPI_VIRTUALATTRS_LOOP_DETECTED (failed to eval a vattr)
 *          SLAPI_VIRTUALATTRS_NOT_FOUND     (type not recognised by any vattr
 *                                              sp && not a real attr in entry )
 *          ENOMEM                           (memory error)
 *
 *    Note:
 *  . modifes the virtual cache in the entry.
 *  . for cached vattrs you always get a copy, so it will need to be
 *     freed via slapi_values_free().
 *
 */

int
slapi_vattr_values_get_sp_ex(vattr_context *c,
                             /* Entry we're interested in */ Slapi_Entry *e,
                             /* attr type name */ char *type,
                             /* pointer to result set */ Slapi_ValueSet ***results,
                             int **type_name_disposition,
                             char ***actual_type_name,
                             int flags,
                             int *buffer_flags,
                             int *item_count)
{
    return slapi_vattr_namespace_values_get_sp(
        c, e, NULL, type, results, type_name_disposition,
        actual_type_name, flags, buffer_flags, item_count);
}

int
slapi_vattr_namespace_values_get_sp(vattr_context *c,
                                    /* Entry we're interested in */ Slapi_Entry *e,
                                    /* DN denoting backend namespace */ Slapi_DN *namespace_dn,
                                    /* attr type name */ char *type,
                                    /* pointer to result set */ Slapi_ValueSet ***results,
                                    int **type_name_disposition,
                                    char ***actual_type_name,
                                    int flags,
                                    int *buffer_flags,
                                    int *item_count)
{

    int rc = 0;
    int sp_bit = 0; /* Set if an SP supplied an answer */
    vattr_sp_handle_list *list = NULL;
    vattr_get_thang *my_get = NULL;
    int attr_count = 0;
    int ignore_vattrs;

    ignore_vattrs = config_get_ignore_vattrs();

    if (!ignore_vattrs) {
        rc = vattr_context_grok(&c);
        if (0 != rc) {
            /* Print a handy error log message */
            if (!vattr_context_is_loop_msg_displayed(&c)) {
                slapi_log_err(SLAPI_LOG_ERR, "slapi_vattr_namespace_values_get_sp",
                              "Detected virtual attribute loop in get on entry %s, attribute %s\n",
                              slapi_entry_get_dn_const(e), type);
                vattr_context_set_loop_msg_displayed(&c);
            }
            return rc;
        }
    }

    /* Having done that, we now consult the attribute map to find service providers who are interested */
    /* Look for attribute in the map */
    if (!(flags & SLAPI_REALATTRS_ONLY) && !ignore_vattrs) {
        /* we use the vattr namespace aware version of this */
        list = vattr_map_namespace_sp_getlist(namespace_dn, type);
        if (list) {
            vattr_sp_handle *current_handle = NULL;
            void *hint = NULL;
            /* first lets consult the cache to save work */
            int cache_status;

            cache_status =
                slapi_entry_vattrcache_find_values_and_type_ex(e, type,
                                                               results,
                                                               actual_type_name);
            switch (cache_status) {
            case SLAPI_ENTRY_VATTR_RESOLVED_EXISTS: /* cached vattr */
            {
                sp_bit = 1;

                /* Complete analysis of type matching */
                *type_name_disposition =
                    (int *)slapi_ch_malloc(sizeof(**type_name_disposition));
                if (0 == slapi_attr_type_cmp(type, **actual_type_name, SLAPI_TYPE_CMP_EXACT)) {
                    **type_name_disposition =
                        SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
                } else {
                    **type_name_disposition =
                        SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_SUBTYPE;
                }

                *item_count = 1;

                break;
            }

            case SLAPI_ENTRY_VATTR_RESOLVED_ABSENT: /* does not exist */
                break;                              /* look in entry */

            case SLAPI_ENTRY_VATTR_NOT_RESOLVED: /* not resolved */
            default:                             /* any other result, resolve */
            {
                /* bit cacky, but need to make a null terminated lists for now
                     * for the (unimplemented and so fake) batch attribute request
                     */
                char **type_list = (char **)slapi_ch_calloc(2, sizeof(char *));
                void **hint_list = (void **)slapi_ch_calloc(2, sizeof(void *));

                type_list[0] = type;

                for (current_handle = vattr_map_sp_first(list, &hint); current_handle; current_handle = vattr_map_sp_next(current_handle, &hint)) {
                    /* call this SP */

                    hint_list[0] = hint;

                    rc = vattr_call_sp_get_batch_values(current_handle, c, e, my_get,
                                                        type_list, results, type_name_disposition,
                                                        actual_type_name, flags, buffer_flags, hint_list);
                    if (0 == rc) {
                        sp_bit = 1; /* this sp provided an answer, we are done */

                        /* count the items in the (null terminated) result list */
                        *item_count = 0;

                        while ((*results)[*item_count]) {
                            (*item_count)++;
                        }

                        break;
                    }
                }

                slapi_ch_free((void **)&type_list);
                slapi_ch_free((void **)&hint_list);

                if (!sp_bit) {
                    /* we have failed and must now examine the entry itself
                         *
                         * But first lets cache the no result
                         * dups the type (if necessary).
                        */
                    slapi_entry_vattrcache_merge_sv(e, type, NULL, *buffer_flags);

                } else {
                    /*
                         * we need to cache the virtual attribute
                         * dups the type (if necessary) and results.
                        */
                    /*
                         * this (and above) will of course need to get updated
                         * when we do real batched attributes
                         */
                    slapi_entry_vattrcache_merge_sv(e, **actual_type_name,
                                                    **results, *buffer_flags);
                }
            }
            }
        }
    }

    /* If no SP supplied the answer, take it from the entry */
    if (!sp_bit && !(flags & SLAPI_VIRTUALATTRS_ONLY)) {
        int counter;

        /* First grok the entry - allocates memory for list */
        attr_count = vattr_helper_get_entry_conts_ex(e, type, &my_get,
                                                     (0 != (flags & SLAPI_VIRTUALATTRS_SUPPRESS_SUBTYPES)));
        *item_count = attr_count;
        rc = 0; /* reset return code (cause an sp must have failed) */

        if (attr_count > 0) {

            *results = (Slapi_ValueSet **)slapi_ch_calloc(1, sizeof(**results) * attr_count);
            *type_name_disposition = (int *)slapi_ch_malloc(sizeof(**type_name_disposition) * attr_count);
            *actual_type_name = (char **)slapi_ch_malloc(sizeof(**actual_type_name) * attr_count);

            /* For attributes which are in the entry, we just need to get to the Slapi_Attr structure and yank out the slapi_value_set
               structure. We either return a pointer directly to it, or we copy it, depending upon whether the caller asked us to try to
               avoid copying.
            */
            for (counter = 0; counter < attr_count; counter++) {
                (*type_name_disposition)[counter] = my_get[counter].get_name_disposition;

                if (my_get[counter].get_present) {
                    if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) {
                        /* pointers will do, good */
                        (*results)[counter] = my_get[counter].get_present_values;
                        (*actual_type_name)[counter] = my_get[counter].get_type_name;
                    } else {
                        /* need to copy the values */
                        (*results)[counter] = valueset_dup(my_get[counter].get_present_values);
                        if (NULL == (*results)[counter]) {
                            rc = ENOMEM;
                        } else {
                            (*actual_type_name)[counter] = slapi_ch_strdup(my_get[counter].get_type_name);
                            if (NULL == (*actual_type_name)[counter]) {
                                rc = ENOMEM;
                            }
                        }
                    }
                    if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) {
                        *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS;
                    } else {
                        *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
                    }
                } else {
                    rc = SLAPI_VIRTUALATTRS_NOT_FOUND;
                }
            }

            slapi_ch_free((void **)&my_get);
        }
    }
    vattr_context_ungrok(&c);
    return rc;
}

/* Do we need a call to free the results from the above ? */

void
slapi_vattr_values_free(Slapi_ValueSet **value, char **actual_type_name, int flags)
{
    /* Check whether we need to free the strings */
    if (flags & SLAPI_VIRTUALATTRS_RETURNED_POINTERS) {
        /* We don't need to free the values */
    } else {
        /* Free the valueset */
        if (*value) {
            slapi_valueset_free(*value);
        }
        if (*actual_type_name) {
            slapi_ch_free((void **)actual_type_name);
        }
    }
    *actual_type_name = NULL;
    *value = NULL;
}

/* Function like the one above but doing a compare operation */
/* Same return codes as above. Compare result value returned in "result": 0 if compare true, 1 if compare false */

int
slapi_vattr_value_compare(Slapi_Entry *e, char *type, Slapi_Value *test_this, int *result, int flags)
{
    return slapi_vattr_namespace_value_compare_sp(NULL, e, NULL, type, test_this, result, flags);
}

/* namespace version of above */
int
slapi_vattr_namespace_value_compare(Slapi_Entry *e, Slapi_DN *namespace_dn, const char *type, Slapi_Value *test_this, int *result, int flags)
{
    return slapi_vattr_namespace_value_compare_sp(NULL, e, namespace_dn, type, test_this, result, flags);
}

int
slapi_vattr_value_compare_sp(vattr_context *c, /* Entry we're interested in */ Slapi_Entry *e, /* attr type name */ char *type, Slapi_Value *test_this, /* pointer to result */ int *result, int flags)
{
    return slapi_vattr_namespace_value_compare_sp(c, e, NULL, type, test_this, result, flags);
}

int
slapi_vattr_namespace_value_compare_sp(vattr_context *c, /* Entry we're interested in */ Slapi_Entry *e, /* backend namespace dn*/ Slapi_DN *namespace_dn, /* attr type name */ const char *type, Slapi_Value *test_this, /* pointer to result */ int *result, int flags)
{
    int rc = 0;

    int sp_bit = 0; /* Set if an SP supplied an answer */
    vattr_sp_handle_list *list = NULL;

    vattr_get_thang *my_get = 0;

    *result = 0; /* return "compare false" by default */

    rc = vattr_context_grok(&c);
    if (0 != rc) {
        if (!vattr_context_is_loop_msg_displayed(&c)) {
            /* Print a handy error log message */
            slapi_log_err(SLAPI_LOG_ERR, "slapi_vattr_namespace_value_compare_sp",
                          "Detected virtual attribute loop in compare on entry %s, attribute %s\n",
                          slapi_entry_get_dn_const(e), type);
            vattr_context_set_loop_msg_displayed(&c);
        }
        return rc;
    }

    /* Having done that, we now consult the attribute map to find service providers who are interested */
    /* Look for attribute in the map */
    list = vattr_map_namespace_sp_getlist(namespace_dn, type);
    if (list) {
        vattr_sp_handle *current_handle = NULL;
        void *hint = NULL;
        for (current_handle = vattr_map_sp_first(list, &hint); current_handle; current_handle = vattr_map_sp_next(current_handle, &hint)) {
            /* call this SP */
            rc = vattr_call_sp_compare_value(current_handle, c, e, my_get, type, test_this, result, flags, hint);
            if (0 == rc) {
                sp_bit = 1;
                break;
            }
        }
    }
    /* If no SP supplied the answer, take it from the entry */
    if (!sp_bit) {
        /* Grok the entry, and remember what we saw. This call does no more than walk down the entry attribute list, do some string compares and copy pointers. */
        int attr_count = vattr_helper_get_entry_conts_ex(e, type, &my_get,
                                                         (0 != (flags & SLAPI_VIRTUALATTRS_SUPPRESS_SUBTYPES)));

        if (my_get && my_get->get_present) {
            int i;
            Slapi_Value *Dummy_value = NULL;

            /* Put the required stuff in the fake attr */

            rc = 0;      /* found real attr - now see if we have the requested value*/
            *result = 0; /* return "compare false" by default */
            for (i = 0; i < attr_count; i++) {
                Dummy_value = slapi_valueset_find(my_get[i].get_attr, my_get[i].get_present_values, test_this);

                if (Dummy_value) {
                    *result = 1; /* return "compare true" */
                    break;
                }
            }
        } else {
            /* no such attribute */
            rc = SLAPI_VIRTUALATTRS_NOT_FOUND;
        }
    }
    vattr_context_ungrok(&c);
    slapi_ch_free((void **)&my_get);
    return rc;
}

/* Function to obtain the list of attribute types which are available on this entry */

/*
    Need to call service providers here:
    We know only the entry's DN and so we could restrict our choice of SPs based on that.
    However, we don't yet implement such fancyness.
    For now, we just crawl through all the SPs, asking them in turn to contribute.
    This is pretty inefficient because we will trawl the list  for each wildcard attribute type search operation.
    Service providers should therefore handle the call as fast as they can.
 */

struct _vattr_type_list_context
{
    unsigned int vattr_context_loop_count;
    vattr_type_thang *types;
    int realattrs_only; /* TRUE implies list contains only real attrs */
    int list_is_copies;
    int flags;
    size_t list_length;
    size_t block_length;
};

/* Helper function which converts a type list from pointers to copies */
static int
vattr_convert_type_list(vattr_type_list_context *thang)
{
    vattr_type_thang *old_list = NULL;
    vattr_type_thang *new_list = NULL;
    size_t index = 0;

    old_list = thang->types;
    /* Make a new list */
    new_list = (vattr_type_thang *)slapi_ch_calloc(thang->block_length, sizeof(vattr_type_thang));
    if (NULL == new_list) {
        return ENOMEM;
    }
    /* Walk the list strdup'ing the type name and copying the rest of the structure contents */
    for (index = 0; index < thang->list_length; index++) {
        new_list[index].type_flags = old_list[index].type_flags;
        new_list[index].type_name = slapi_ch_strdup(old_list[index].type_name);
        /*
         * list_is_copies does not affect type_values as it
         * is always a pointer never a copy.
         */
        new_list[index].type_values = old_list[index].type_values;
    }
    /* Mark it a copy list */
    thang->list_is_copies = 1;
    /* Free the original list */
    slapi_ch_free((void **)&(thang->types));
    /* swap lists */
    thang->types = new_list;

    return 0;
}

/*
 * Returns all the attribute types from e, both real and virtual.
 *
 * Any of the attributes found in the entry to start with, for
 * whom no vattr SP volunteered, will also have a pointer to the
 * original valueset in the entry--this fact can be used by
 * calling slapi_vattr_values_type_thang_get(), which will
 * take the values present in the vattr_type_thang list
 * rather than calling slapi_vattr_values_get() to retrieve the value.
*/

#define TYPE_LIST_EXTRA_SPACE 5 /* Number of extra slots we allow for SP's to insert types */

int
slapi_vattr_list_attrs(/* Entry we're interested in */ Slapi_Entry *e,
                       /* pointer to receive the list */ vattr_type_thang **types,
                       int flags,
                       int *buffer_flags)
{
    int rc = 0;
    size_t attr_count = 0;
    vattr_type_thang *result_array = NULL;
    Slapi_Attr *current_attr = NULL;
    size_t i = 0;
    int list_is_copies = 0;
    vattr_sp_handle_list *list = NULL;
    size_t list_length = 0;
    size_t block_length = 0;
    vattr_type_list_context type_context = {0};

    if (NULL == types || NULL == buffer_flags) {
        slapi_log_err(SLAPI_LOG_ERR, "slapi_vattr_list_attrs", "Invalid param\n");
        return -1;
    }

    block_length = 1 + TYPE_LIST_EXTRA_SPACE;

    if (!(flags & SLAPI_VIRTUALATTRS_ONLY)) {
        /* First find what's in the entry itself*/
        /* Count the attributes */
        for (current_attr = e->e_attrs; current_attr != NULL; current_attr = current_attr->a_next, attr_count++)
            ;
        block_length += attr_count;
        /* Allocate the pointer array */
        result_array = (vattr_type_thang *)slapi_ch_calloc(block_length, sizeof(vattr_type_thang));
        if (NULL == result_array) {
            return ENOMEM;
        }
        list_length = attr_count;

        /* Now copy the pointers into the array */
        i = 0;
        for (current_attr = e->e_attrs; current_attr != NULL; current_attr = current_attr->a_next) {
            char *mypointer = NULL;
            if (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS) {
                mypointer = current_attr->a_type;
                result_array[i].type_values = &(current_attr->a_present_values);
            } else {
                mypointer = slapi_ch_strdup(current_attr->a_type);
                list_is_copies = 1;
            }
            if (NULL == current_attr->a_plugin) {
                /* could be lazy plugin initialization, get it now */
                slapi_attr_init_syntax(current_attr);
            }
            result_array[i].type_flags = current_attr->a_flags;
            result_array[i++].type_name = mypointer;
        }
        PR_ASSERT(i == attr_count);
    }

    /* if we didnt send real attrs we need to allocate result array */
    if (NULL == result_array) {
        result_array = (vattr_type_thang *)slapi_ch_calloc(block_length, sizeof(vattr_type_thang));
        if (NULL == result_array) {
            return ENOMEM;
        }
    }

    /* Then ask the service providers for their input */
    type_context.flags = flags;
    type_context.realattrs_only = 1; /* until we know otherwise */
    type_context.list_is_copies = list_is_copies;
    type_context.types = result_array;
    type_context.list_length = list_length;
    type_context.block_length = block_length;

    /*
     * At this point type_context.types is a list of all
     * the attributetypes (copies or pointers) and values (copies, if present)
     * found in the entry.
    */

    if (!(flags & SLAPI_REALATTRS_ONLY)) {
        list = vattr_map_sp_get_complete_list();
        if (list) {
            vattr_sp_handle *current_handle = NULL;

            for (current_handle = vattr_list_sp_first(list); current_handle; current_handle = vattr_list_sp_next((vattr_sp_handle_list *)current_handle)) {
                /* call this SP */
                rc = vattr_call_sp_get_types(current_handle, e, &type_context,
                                             flags);
                if (0 != rc) {
                    /* DBDB do what on error condition ? */
                }
            }
            /* assert enough space for the null terminator */
            PR_ASSERT(type_context.list_length < type_context.block_length);
            i = type_context.list_length;
        }
    }

    /*
     * Now type_context.types is a list of all the types in this entry--
     * real and virtual.  For real ones, the values field is filled in.
     * For virtual ones (including virtual ones which will overwrite a real
     * value) the values field is null--this fact may used
     * subsequently by callers of slapi_vattr_list_attrs()
     * by calling slapi_vattr_values_type_thang_get() which
     * will only calculate the values for attributetypes with
     * non-null values.
    */

    flags = type_context.flags;
    list_is_copies = type_context.list_is_copies;
    result_array = type_context.types;
    list_length = type_context.list_length;
    block_length = type_context.block_length;

    if (list_is_copies) {
        *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
    } else {
        *buffer_flags = SLAPI_VIRTUALATTRS_RETURNED_POINTERS;
    }

    /* Let the caller know if the list contains only real attrs */
    if (type_context.realattrs_only) {
        *buffer_flags |= SLAPI_VIRTUALATTRS_REALATTRS_ONLY;
    }

    result_array[i].type_name = NULL;

    if (list_length)
        *types = result_array;
    else
        *types = 0;

    return rc;
}

static int
vattr_add_thang_to_list(vattr_type_list_context *c, vattr_type_thang *thang)
{
    vattr_type_thang *new_list = NULL;

    /* Will it fit in the current block ? */
    if (c->list_length == c->block_length - 1) {
        c->block_length *= 2;

        new_list = (vattr_type_thang *)slapi_ch_realloc((char *)c->types,
                                                        c->block_length * (int)sizeof(vattr_type_thang));
        if (NULL == new_list) {
            return ENOMEM;
        }

        c->types = new_list;
    }
    /* Add to the end of the list */
    c->types[c->list_length] = *thang;
    c->list_length += 1;
    return 0;
}

/* Called by SP's during their get_types call, to have the server add a
 * type to the type list.
 *
 * c: the vattr_type_list_context passed from the original caller of
 *        slapi_vattr_list_attrs()
 * thang: the new type to be added to the type list.
 * flags: tells the routine whether to copy the thang or not.
 *            SLAPI_VIRTUALATTRS_REQUEST_POINTERS--no need to copy it.
 *            !(SLAPI_VIRTUALATTRS_REQUEST_POINTERS)--need to copy it.
 *
 * Checks to see if the requested type is already in the list,
 * and if it is, if the value of the attribute is
 * non-null, it resets the value to null.  This means that when used
 * subsequently, the type list indicates whether, for a given attribute type,
 * the SP's need to be called to retrieve the value.
 *
*/
int
slapi_vattrspi_add_type(vattr_type_list_context *c,
                        vattr_type_thang *thang,
                        int flags)
{
    int rc = 0;
    unsigned int index = 0;
    vattr_type_thang thang_to_add = {0};

    int found_it = 0;

    PR_ASSERT(c);

    /* We are no longer sure that the list contains only real attrs */
    c->realattrs_only = 0;

    /* Check to see if the type is in the list already */
    for (index = 0; index < c->list_length; index++) {
        if (0 == slapi_UTF8CASECMP(c->types[index].type_name, thang->type_name)) {
            found_it = 1;
            break;
        }
    }
    /*
     * If found and there are values specified in the vattr_type_thang, then
     * that means it's a real attribute--because SP's do not add values
     * when called via slapi_vattr_list_attrs()/vattr_call_sp_get_types().
     * However, an SP is willing to claim responsibility for this attribute
     * type, so to ensure that that virtual value will get calculated, set the
     * original real value to NULL here.
     * The guiding rule here is "if an SP is prepared
     * to provide a value for an attribute type, then that is the one
     * that must be returned to the user".  Note, this does not prevent
     * the SP implementing a "merge real with virtual policy", if they
     * wish.
    */

    if (found_it) {

        if (c->types[index].type_values != NULL) {
            /*
                 * Any values in this list are always pointers.
                 * See slapi_vattr_list_types() and vattr_convert_type_list()
                */
            c->types[index].type_values = NULL;
        }

        return 0;
    }
    /*
     * If it's not in the list then we need to add it.
     * If the list is already copies, then we need to make a copy of what the
     * SP passed us.
     * If the SP indicated that the type name it passed us is volatile,
     * !(flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS)
     * then we need to copy it anyway.
    */

    /* rbyrneXXX optimisation for COS: if each type_thang could have a
     * free_flag
     * then we would not have to convert all the real attrs to copies
     * just because an SP returned a copy (note: only COS rrequires copies;
     * roles returns a pointer to a static string "nsRole").
    */
    if (!c->list_is_copies &&
        (0 == (flags & SLAPI_VIRTUALATTRS_REQUEST_POINTERS))) {
        /* Means we need to turn the existing list into a list of copies */
        vattr_convert_type_list(c);
    }
    if (c->list_is_copies) {
        thang_to_add.type_flags = thang->type_flags;
        thang_to_add.type_name = slapi_ch_strdup(thang->type_name);
    } else {
        thang_to_add = *thang;
    }
    /* Lastly, we add to the list, using a function which hides the complexity of extending the list if it fills up */
    vattr_add_thang_to_list(c, &thang_to_add);

    return rc;
}

/* Function to free the list returned in the function above */
/* Written, reviewed, debug stepped */

void
slapi_vattr_attrs_free(vattr_type_thang **types, int flags)
{
    if (NULL == *types) {
        return;
    }
    /* Check whether we need to free the strings */
    if (flags & SLAPI_VIRTUALATTRS_RETURNED_POINTERS) {
        /* We don't need to free the values */
    } else {
        char *attr_name_to_free = NULL;
        size_t i = 0;
        /* Walk down the set of values, fr eeing each one in turn */
        for (; (attr_name_to_free = (*types)[i].type_name) != NULL; i++) {
            slapi_ch_free((void **)&attr_name_to_free);
        }
    }
    /* We always need to free the pointer block */
    slapi_ch_free((void **)types);
}

char *
vattr_typethang_get_name(vattr_type_thang *t)
{
    return t->type_name;
}

unsigned long
vattr_typethang_get_flags(vattr_type_thang *t)
{
    return t->type_flags;
}

vattr_type_thang *
vattr_typethang_next(vattr_type_thang *t)
{
    t++;
    if (t->type_name) {
        return t;
    } else {
        return NULL;
    }
}

vattr_type_thang *
vattr_typethang_first(vattr_type_thang *t)
{
    return t;
}

vattr_get_thang *
slapi_vattr_getthang_first(vattr_get_thang *t)
{
    return t;
}

vattr_get_thang *
slapi_vattr_getthang_next(vattr_get_thang *t)
{
    t++;
    if (t->get_present) {
        return t;
    } else {
        return NULL;
    }
}


/* End of the public interface functions */

/* Everything below here is the SPI interface, only callable by vattr service providers.
   Currently this interface is not public. Interface definitions are in vattr_spi.h
 */

/* Structures used in the SPI interface */

/* Service provider object */


struct _vattr_sp
{
    /* vtbl */
    vattr_get_fn_type sp_get_fn;
    vattr_get_ex_fn_type sp_get_ex_fn;
    vattr_compare_fn_type sp_compare_fn;
    vattr_types_fn_type sp_types_fn;
    void *sp_data; /* Pointer for use by the Service Provider */
};
typedef struct _vattr_sp vattr_sp;

/* Service provider handle */
struct _vattr_sp_handle
{
    vattr_sp *sp;
    struct _vattr_sp_handle *next; /* So we can link them together in the map */
    void *hint;                    /* Hint to the SP */
    uint64_t rc;
};

/* Calls made by Service Providers */
/* Call to register as a service provider */
/* This function needs to do the following:
    o Let the provider say "hey, I'm here". It should probably get some handle back which it can use in future calls.
    o Say whether it wants to be called to resolve every single query, or whether it will say in advance what attrs it services.
 */

static vattr_sp_handle *vattr_sp_list = NULL;

int
slapi_vattrspi_register(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn)
{
    return slapi_vattrspi_register_internal(h, get_fn, 0, compare_fn, types_fn, 0);
}

int
slapi_vattrspi_register_ex(vattr_sp_handle **h, vattr_get_ex_fn_type get_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options)
{
    return slapi_vattrspi_register_internal(h, 0, get_fn, compare_fn, types_fn, options);
}

/* options not used yet - for future expansion */
int
slapi_vattrspi_register_internal(vattr_sp_handle **h, vattr_get_fn_type get_fn, vattr_get_ex_fn_type get_ex_fn, vattr_compare_fn_type compare_fn, vattr_types_fn_type types_fn, void *options __attribute__((unused)))
{
    vattr_sp_handle *return_to_caller = NULL;
    vattr_sp_handle *list_handle = NULL;
    vattr_sp *new_sp = NULL;

    /* Make a service provider handle */
    new_sp = (vattr_sp *)slapi_ch_calloc(1, sizeof(vattr_sp));
    new_sp->sp_get_fn = get_fn;
    new_sp->sp_get_ex_fn = get_ex_fn;
    new_sp->sp_compare_fn = compare_fn;
    new_sp->sp_types_fn = types_fn;

    return_to_caller = (vattr_sp_handle *)slapi_ch_calloc(1, sizeof(vattr_sp_handle));
    return_to_caller->sp = new_sp;

    /* Add to the service provider list */
    /* Make a handle for the list */
    list_handle = (vattr_sp_handle *)slapi_ch_calloc(1, sizeof(vattr_sp_handle));
    *list_handle = *return_to_caller;
    list_handle->next = vattr_sp_list;
    vattr_sp_list = list_handle;

    /* Return the handle to the caller */
    *h = return_to_caller;
    return 0;
}

vattr_sp_handle_list *
vattr_map_sp_get_complete_list(void)
{
    return vattr_sp_list;
}

int
slapi_vattrspi_regattr(vattr_sp_handle *h, char *type_name_to_register, char *DN, void *hint)
{
    int ret = 0;
    char *type_to_add;
    int free_type_to_add = 0;
    Slapi_DN original_dn;
    int ignore_vattrs;

    ignore_vattrs = config_get_ignore_vattrs();

    slapi_sdn_init(&original_dn);

    /* Supplying a DN means that the plugin requires to be called
     * only when the considering attributes in relevant entries - almost
     *
     * Actually the smallest namespace is the backend.
     *
     * atttributes that are tied to backends have the format DN::attr
     * this essentially makes the attribute have split namespaces
     * that are treated as separate attributes in the vattr code
     */
    if (DN) {
        /* as a coutesy we accept any old DN and will convert
         * to a namespace DN, this helps to hide details
         * (that we might decide to change) anyway
         */
        Slapi_Backend *be;
        Slapi_DN *namespace_dn;

        slapi_sdn_set_dn_byref(&original_dn, DN);
        be = slapi_be_select(&original_dn);
        namespace_dn = (Slapi_DN *)slapi_be_getsuffix(be, 0);

        if (namespace_dn && be != defbackend_get_backend()) /* just in case someone thinks "" is a good namespace */
        {
            type_to_add = slapi_ch_smprintf("%s::%s",
                                            (char *)slapi_sdn_get_dn(namespace_dn),
                                            type_name_to_register);

            free_type_to_add = 1;
        } else {
            type_to_add = type_name_to_register;
        }

    } else {
        type_to_add = type_name_to_register;
    }

    ret = vattr_map_sp_insert(type_to_add, h, hint);

    slapi_sdn_done(&original_dn);
    if (free_type_to_add) {
        slapi_ch_free((void **)&type_to_add);
    }
    if (ignore_vattrs && strcasecmp(type_name_to_register, NSROLEATTR)) {
        /* A new virtual attribute is registered.
         * This new vattr being *different* than the default roles vattr 'nsRole'
         * It is time to allow vattr lookup
         */
        char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE];
        errorbuf[0] = '\0';
        config_set_ignore_vattrs(CONFIG_IGNORE_VATTRS, "off", errorbuf, 1);
        slapi_log_err(SLAPI_LOG_INFO,
                      "slapi_vattrspi_regattr",
                      "Because %s is a new registered virtual attribute , %s was set to 'off'\n",
                      type_name_to_register,
                      CONFIG_IGNORE_VATTRS);
    }

    return ret;
}


/* Call to advertise or refute that you generate the value for some attribute, and to signal that the definition for this attribute has changed */
/* This call needs to do the following:
    o Let the SP say "you know, I will generate the value of attribute "foo", within subtree "bar". (we might ignore the subtree for now to keep things simple
    o Same as above, but say "you know, I'm not doing that any more". (we'll do that with a addordel flag).
    o Say, "you know that definition I said I did, well it's changed".
*/

/* Functions to handle the context stucture */

int
slapi_vattr_context_create(vattr_context **c __attribute__((unused)))
{
    return 0;
}

void
slapi_vattr_context_destroy(vattr_context *c __attribute__((unused)))
{
    return;
}

int
vattr_call_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get __attribute__((unused)), char *type, Slapi_ValueSet **results, int *type_name_disposition, char **actual_type_name, int flags, int *buffer_flags, void *hint)
{
    int ret = -1;

    if (handle->sp->sp_get_fn) {
        ret = ((handle->sp->sp_get_fn)(handle, c, e, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, hint));
    }

    return ret;
}

int
vattr_call_sp_get_batch_values(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get __attribute__((unused)), char **type, Slapi_ValueSet ***results, int **type_name_disposition, char ***actual_type_name, int flags, int *buffer_flags, void **hint)
{
    int ret = 0;

    if (handle->sp->sp_get_ex_fn) {
        ret = ((handle->sp->sp_get_ex_fn)(handle, c, e, type, results, type_name_disposition, actual_type_name, flags, buffer_flags, hint));
    } else {
        /* make our args look like the simple non-batched case */
        *results = (Slapi_ValueSet **)slapi_ch_calloc(2, sizeof(**results)); /* 2 for null terminated list */
        *type_name_disposition = (int *)slapi_ch_calloc(2, sizeof(**type_name_disposition));
        *actual_type_name = (char **)slapi_ch_calloc(2, sizeof(**actual_type_name));

        ret = ((handle->sp->sp_get_fn)(handle, c, e, *type, *results, *type_name_disposition, *actual_type_name, flags, buffer_flags, hint));
        if (ret) {
            slapi_ch_free((void **)results);
            slapi_ch_free((void **)type_name_disposition);
            slapi_ch_free((void **)actual_type_name);
        }
    }

    return ret;
}

int
vattr_call_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, vattr_get_thang *my_get __attribute__((unused)), const char *type, Slapi_Value *test_this, int *result, int flags, void *hint)
{
    return ((handle->sp->sp_compare_fn)(handle, c, e, (char *)type, test_this, result, flags, hint));
}

int
vattr_call_sp_get_types(vattr_sp_handle *handle, Slapi_Entry *e, vattr_type_list_context *type_context, int flags)
{
    return ((handle->sp->sp_types_fn)(handle, e, type_context, flags));
}

/* Service provider entry point prototypes */

/* Call which allows a SP to say what attributes can be used to define a given attribute, used for loop detection */
/* Not implementing this yet */
/* typedef int (*vattrspi_explain_definition) (); */

/* Function called to indicate to the SP that the game is over, we free the handle, not the SP */
typedef int (*vattrspi_deregister)(vattr_sp_handle *h);

/* End of the SPI functions */

/* Implementation functions only after here */

/* The attribute map */

/* We need a structure which can map attribute type names to SP's which might tell us
something useful. We need a function which, given an attr type name, can return
a set of SP's to ask about said attr. SP's which don't tell us which attrs they are
handling always get asked. The first SP which tells us the answer wins, we don't
attempt to arbitrate between them. One might imaging doing this stuff in a unified
way with other stuff pertaining to types (schema, syntax). Someday. For now this
is a separate thing in the insterests of stability.
*/

/* Virtual Attribute map implementation */

/* The virtual attribute type map is implemented as a hash table.
   The single key is the attribute type base name (not an alias)
   (what about subtypes??). The hash table takes care of translating
   this key to a list of probable service providers for the type.

   Other than the obvious lookup function, we need functions to
   allow entries to be added and removed from the map.

   Because the map is likely to be consulted at least once for every search
   operation performed by the server, it's important that it is free from
   performance deficiencies such as lock contention (is the NSPR hash table
   implementation even thread safe ?

 */

#define VARRT_MAP_HASHTABLE_SIZE 32

/* Attribute map oject */
/* Needs to contain: a linked list of pointers to provider handles handles,
        The type name,
        other stuff ?

   Access to the entire map will be controlled via a single RW lock.
 */

struct _objAttrValue
{
    struct _objAttrValue *pNext;
    Slapi_Value *val;
};
typedef struct _objAttrValue objAttrValue;

struct _vattr_map_entry
{
    char * /* currect ? */ type_name;
    vattr_sp_handle *sp_list;
    objAttrValue *objectclasses; /* objectclasses for this type to check schema with*/
};
typedef struct _vattr_map_entry vattr_map_entry;


vattr_map_entry test_entry = {NULL};

static PRIntn
vattr_hash_compare_keys(const void *v1, const void *v2)
{
    /* Should this be subtype aware, etc ? */
    return ((0 == strcasecmp(v1, v2)) ? 1 : 0);
}

static PRIntn
vattr_hash_compare_values(const void *v1, const void *v2)
{
    return (((char *)v1 == (char *)v2) ? 1 : 0);
}

static PLHashNumber
vattr_hash_fn(const void *type_name)
{
    /* need a hash function here */
    /* Sum the bytes for now */
    PLHashNumber result = 0;
    char *current_position = NULL;
    char current_char = 0;
    for (current_position = (char *)type_name; *current_position; current_position++) {
        current_char = tolower(*current_position);
        result += current_char;
    }
    return result;
}

static int
vattr_map_create(void)
{
    the_map = (vattr_map *)slapi_ch_calloc(1, sizeof(vattr_map));
    if (NULL == the_map) {
        slapd_nasty(sourcefile, 1, 0);
        return ENOMEM;
    }

    the_map->hashtable = PL_NewHashTable(VARRT_MAP_HASHTABLE_SIZE,
                                         vattr_hash_fn, vattr_hash_compare_keys,
                                         vattr_hash_compare_values, NULL, NULL);

    if (NULL == the_map->hashtable) {
        slapd_nasty(sourcefile, 2, 0);
        return ENOMEM;
    }

    the_map->lock = slapi_new_rwlock_prio(1 /* priority on writers */);
    if (NULL == the_map) {
        slapd_nasty(sourcefile, 3, 0);
        return ENOMEM;
    }
    return 0;
}

/*
    vattr_delete_attrvals
    ---------------------
    deletes a value list
*/
void
vattr_delete_attrvals(objAttrValue **attrval)
{
    objAttrValue *val = *attrval;

    while (val) {
        objAttrValue *next = val->pNext;
        slapi_value_free(&val->val);
        slapi_ch_free((void **)&val);
        val = next;
    }
}

void
vattr_map_entry_free(vattr_map_entry *vae)
{
    vattr_sp_handle *list_entry = vae->sp_list;
    while (list_entry != NULL) {
        vattr_sp_handle *next_entry = list_entry->next;
        if (slapi_atomic_decr_64(&(list_entry->rc), __ATOMIC_RELAXED) == 0) {
            /* Only free on RC 0 */
            slapi_ch_free((void **)&list_entry);
        }
        list_entry = next_entry;
    }
    if (vae->objectclasses) {
        vattr_delete_attrvals(&(vae->objectclasses));
    }
    slapi_ch_free_string(&(vae->type_name));
    slapi_ch_free((void **)&vae);
}

static PRIntn
vattr_he_cleanup_fn(PLHashEntry *he, PRIntn index __attribute__((unused)), void *arg __attribute__((unused)))
{
    vattr_map_entry_free((vattr_map_entry *)he->value);
    he->value = NULL;
    return HT_ENUMERATE_NEXT;
}

static void
vattr_map_destroy(void)
{
    if (the_map) {
        PL_HashTableEnumerateEntries(the_map->hashtable, vattr_he_cleanup_fn, NULL);
        if (the_map->hashtable) {
            PL_HashTableDestroy(the_map->hashtable);
        }
        if (the_map->lock) {
            slapi_destroy_rwlock(the_map->lock);
        }
    }
    slapi_ch_free((void **)&the_map);
}

/* Returns 0 if present, entry returned in result. Returns SLAPI_VIRTUALATTRS_NOT_FOUND if not found */
static int
vattr_map_lookup(const char *type_to_find, vattr_map_entry **result)
{
    char *basetype = 0;
    char *tmp = 0;
    char buf[SLAPD_TYPICAL_ATTRIBUTE_NAME_MAX_LENGTH];

    *result = NULL;
    PR_ASSERT(the_map);

    /* vattrs need to support subtypes
     * we insist that one service provider
     * will support all subtypes for a
     * given superior, hence we look for
     * superiors only here
     */

    tmp = slapi_attr_basetype(type_to_find, buf, sizeof(buf));
    if (tmp) {
        basetype = tmp;
    } else {
        basetype = buf;
    }

    /* Get the reader lock */
    slapi_rwlock_rdlock(the_map->lock);
    *result = (vattr_map_entry *)PL_HashTableLookupConst(the_map->hashtable,
                                                         (void *)basetype);
    /* Release ze lock */
    slapi_rwlock_unlock(the_map->lock);

    if (tmp) {
        slapi_ch_free_string(&tmp);
    }

    if (*result) {
        return 0;
    } else {
        return SLAPI_VIRTUALATTRS_NOT_FOUND;
    }
}

/* Insert an entry into the attribute map */
int
vattr_map_insert(vattr_map_entry *vae)
{
    PR_ASSERT(the_map);
    /* Get the writer lock */
    slapi_rwlock_wrlock(the_map->lock);
    /* Insert the thing */
    /* It's illegal to call this function if the entry is already there */
    PR_ASSERT(NULL == PL_HashTableLookupConst(the_map->hashtable, (void *)vae->type_name));
    PL_HashTableAdd(the_map->hashtable, (void *)vae->type_name, (void *)vae);
    /* Unlock and we're done */
    slapi_rwlock_unlock(the_map->lock);
    return 0;
}

/*
    vattr_add_attrval
    -----------------
    adds a value to an attribute value list
*/
int
vattr_add_attrval(objAttrValue **attrval, char *val)
{
    int ret = 0;
    objAttrValue *theVal;

    slapi_log_err(SLAPI_LOG_TRACE, "vattr_add_attrval", "-->\n");

    /* create the attrvalue */
    theVal = (objAttrValue *)slapi_ch_malloc(sizeof(objAttrValue));
    if (theVal) {
        theVal->val = slapi_value_new_string(val);
        if (theVal->val) {
            theVal->pNext = *attrval;
            *attrval = theVal;
        } else {
            slapi_ch_free((void **)&theVal);
            slapi_log_err(SLAPI_LOG_ERR, "vattr_add_attrval", "Failed to allocate memory\n");
            ret = -1;
        }
    } else {
        slapi_log_err(SLAPI_LOG_ERR, "vattr_add_attrval", "Failed to allocate memory\n");
        ret = -1;
    }

    slapi_log_err(SLAPI_LOG_TRACE, "vattr_add_attrval", "-->\n");
    return ret;
}


objAttrValue *
vattr_map_entry_build_schema(char *type_name)
{
    objAttrValue *ret = 0;
    struct objclass *oc;

    slapi_log_err(SLAPI_LOG_TRACE, "vattr_map_entry_build_schema", "-->\n");

    /* this is the first opportunity to register
     * with the statechange api, our init function
     * gets called prior to loading plugins, so it
     * was not available then
     */
    if (!statechange_api) {
        /* grab statechange api - we never release this */
        if (!slapi_apib_get_interface(StateChange_v1_0_GUID, &statechange_api)) {
            /* register for schema changes via dn */
            statechange_register(statechange_api, "vattr", "cn=schema", NULL, NULL, (notify_callback)schema_changed_callback);
        }
    }

    if (!config_get_schemacheck()) {
        slapi_log_err(SLAPI_LOG_TRACE, "vattr_map_entry_build_schema", "<-- schema off\n");
        return 0;
    }

    oc_lock_read();
    for (oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next) {
        char **pppAttrs[2];
        int index;
        int attrType = 0;
        int oc_added = 0;

        pppAttrs[0] = oc->oc_required;
        pppAttrs[1] = oc->oc_allowed;

        /* we need to check both required and allowed attributes */
        while (!oc_added && attrType < 2) {
            if (pppAttrs[attrType]) {
                index = 0;

                while (pppAttrs[attrType][index]) {
                    if (!PL_strcasecmp(pppAttrs[attrType][index], type_name)) {
                        vattr_add_attrval(&ret, oc->oc_name);
                        oc_added = 1;
                        break;
                    }
                    index++;
                }
            }
            attrType++;
        }
    }
    oc_unlock();

    slapi_log_err(SLAPI_LOG_TRACE, "vattr_map_entry_build_schema", "<--\n");
    return ret;
}

static PRIntn
vattr_map_entry_rebuild_schema(PLHashEntry *he, PRIntn i __attribute__((unused)), void *arg __attribute__((unused)))
{
    vattr_map_entry *entry = (vattr_map_entry *)(he->value);

    if (entry->objectclasses)
        vattr_delete_attrvals(&(entry->objectclasses));

    entry->objectclasses = vattr_map_entry_build_schema(entry->type_name);

    return HT_ENUMERATE_NEXT;
}

void
schema_changed_callback(Slapi_Entry *e __attribute__((unused)),
                        char *dn __attribute__((unused)),
                        int modtype __attribute__((unused)),
                        Slapi_PBlock *pb __attribute__((unused)),
                        void *caller_data __attribute__((unused)))
{
    /* Get the writer lock */
    slapi_rwlock_wrlock(the_map->lock);

    /* go through the list */
    PL_HashTableEnumerateEntries(the_map->hashtable, vattr_map_entry_rebuild_schema, 0);

    /* Unlock and we're done */
    slapi_rwlock_unlock(the_map->lock);
}


int
slapi_vattr_schema_check_type(Slapi_Entry *e, char *type)
{
    int ret = 0;
    vattr_map_entry *map_entry;

    if (config_get_schemacheck()) {
        Slapi_Attr *attr;

        if (0 == slapi_entry_attr_find(e, "objectclass", &attr)) {
            Slapi_ValueSet *vs;

            if (0 == slapi_attr_get_valueset(attr, &vs)) {
                objAttrValue *obj;

                if (0 == vattr_map_lookup(type, &map_entry)) {
                    slapi_rwlock_rdlock(the_map->lock);

                    obj = map_entry->objectclasses;

                    while (obj) {
                        if (slapi_valueset_find(attr, vs, obj->val)) {
                            /* this entry has an objectclass
                             * that allows or requires the
                             * attribute type
                             */
                            ret = -1;
                            break;
                        }

                        obj = obj->pNext;
                    }

                    slapi_rwlock_unlock(the_map->lock);
                }

                slapi_valueset_free(vs);
            }
        }

    } else
        ret = -1;

    return ret;
}

vattr_map_entry *
vattr_map_entry_new(char *type_name, vattr_sp_handle *sph, void *hint)
{
    vattr_map_entry *result = (vattr_map_entry *)slapi_ch_calloc(1, sizeof(vattr_map_entry));
    result->type_name = slapi_ch_strdup(type_name);
    result->sp_list = sph;

    /* go get schema */
    result->objectclasses = vattr_map_entry_build_schema(type_name);

    return result;
}

/* On top of the map, we need functions to manipulate the SP handles from within */

/* Function to get the SP list from the map, given a type name */
/* The resulting list is in the map, but is safe to read regardless of concurrent updates */
vattr_sp_handle_list *
vattr_map_sp_getlist(char *type_to_find)
{
    int ret = 0;
    vattr_map_entry *result = NULL;
    ret = vattr_map_lookup(type_to_find, &result);
    if (0 == ret) {
        return (vattr_sp_handle_list *)result->sp_list;
    } else {
        return NULL;
    }
}

/* same as above, but filters the list based on the supplied backend dn
 * when we stored these dn based attributes, we concatenated them with
 * the dn like this dn::attribute, so we need to do two checks for the
 * attribute, one with, and one without the dn
 */
vattr_sp_handle_list *
vattr_map_namespace_sp_getlist(Slapi_DN *dn, const char *type_to_find)
{
    int ret = 0;
    vattr_map_entry *result = NULL;
    vattr_sp_handle_list *return_list = 0;

    if (config_get_ignore_vattrs()) {
        /* we don't care about vattrs */
        return NULL;
    }

    ret = vattr_map_lookup(type_to_find, &result);
    if (0 == ret) {
        return_list = (vattr_sp_handle_list *)result->sp_list;
    } else {
        /* we have allowed the global namespace provider a shot
         * now it is time to query for split namespace providers
         */
        if (dn) {
            char *split_dn = (char *)slapi_sdn_get_dn(dn);
            char *split_type_to_find =
                slapi_ch_smprintf("%s::%s", split_dn, type_to_find);

            if (split_type_to_find) {
                ret = vattr_map_lookup(split_type_to_find, &result);
                if (0 == ret) {
                    return_list = (vattr_sp_handle_list *)result->sp_list;
                }

                slapi_ch_free((void **)&split_type_to_find);
            }
        }
    }

    return return_list;
}


/* Iterator function for the list */
vattr_sp_handle *
vattr_map_sp_next(vattr_sp_handle_list *list, void **hint)
{
    vattr_sp_handle *result = NULL;
    vattr_sp_handle *current = (vattr_sp_handle *)list;
    PR_ASSERT(list);
    PR_ASSERT(hint);
    result = current->next;
    *hint = current->hint;
    return result;
}

/* Iterator function for the list */
vattr_sp_handle *
vattr_map_sp_first(vattr_sp_handle_list *list, void **hint)
{
    vattr_sp_handle *result = NULL;
    PR_ASSERT(list);
    PR_ASSERT(hint);
    result = (vattr_sp_handle *)list;
    *hint = result->hint;
    return result;
}

/* Iterator function for the list */
vattr_sp_handle *
vattr_list_sp_next(vattr_sp_handle_list *list)
{
    vattr_sp_handle *result = NULL;
    vattr_sp_handle *current = (vattr_sp_handle *)list;
    PR_ASSERT(list);
    result = current->next;
    return result;
}

/* Iterator function for the list */
vattr_sp_handle *
vattr_list_sp_first(vattr_sp_handle_list *list)
{
    vattr_sp_handle *result = NULL;
    PR_ASSERT(list);
    result = (vattr_sp_handle *)list;
    return result;
}

/* Function to insert an SP into the map entry for a given type name */
/* Note that SP's can't ever remmove themselves from the map---if they could
we'd need to hold a lock on the read path, which we don't want to do.
So any SP which relinquishes its need to handle a type needs to continue
to handle the calls on it, but return nothing */
/* DBDB need to sort out memory ownership here, it's not quite right */
/*
 * This function was inconsistent. We would allocated and "kind of",
 * copy the sp_handle here for the vattr_map_entry_new path. But we
 * would "take ownership" for the existing entry and the list addition
 * path. Instead now, EVERY sp_handle we take, we take ownership of
 * and the CALLER must allocate a new one each time.
 *
 * Better idea, is that regattr should just take the fn pointers
 * and callers never *see* the sp_handle structure at all.
 *
 * This leaves us with some quirks today. First: if you have plugin A
 * and B, A registers attr 1 and B 1 and 2, it's possible that if you
 * register A1 first, then B1, you have B->A in next. Then when you
 * register B2, because we take 0==result from map_lookup, we add sp
 * "as is" to the map. This means that B2 now has the same next to A1
 * handle. This won't add a bug, because A1 won't be able to service the
 * attr, but it could cause some head scratching ...
 *
 * Again, to fix this, the whole vattr external interface needs a
 * redesign ... :(
 */

int
vattr_map_sp_insert(char *type_to_add, vattr_sp_handle *sp, void *hint)
{
    int result = 0;
    vattr_map_entry *map_entry = NULL;
    /* Is this type already there ? */
    result = vattr_map_lookup(type_to_add, &map_entry);
    /* If it is, add this SP to the list, safely even if readers are traversing the list at the same time */
    if (0 == result) {
        int found = 0;
        vattr_sp_handle *list_entry = NULL;
        /* Walk the list checking that the daft SP isn't already here */
        for (list_entry = map_entry->sp_list; list_entry; list_entry = list_entry->next) {
            if (list_entry == sp) {
                found = 1;
                break;
            }
        }
        /* If it is, we do nothing */
        if (found) {
            return 0;
        }
        /* Increase the ref count of the sphandle */
        slapi_atomic_incr_64(&(sp->rc), __ATOMIC_RELAXED);
        /* We insert the SP handle into the linked list at the head */
        sp->next = map_entry->sp_list;
        map_entry->sp_list = sp;
    } else {
        /* If not, add it */
        /* Claim a reference on the sp ... */
        slapi_atomic_incr_64(&(sp->rc), __ATOMIC_RELAXED);
        map_entry = vattr_map_entry_new(type_to_add, sp, hint);
        if (NULL == map_entry) {
            return ENOMEM;
        }
        return vattr_map_insert(map_entry);
    }
    return 0;
}

/*
 * returns non-zero if type is a cachable virtual attr, zero otherwise.
 *
 * Decision point for caching vattrs...to be expanded to be configurable,
 * allow sp's to have a say etc.
*/

static int cache_all = 0;

int
slapi_vattrcache_iscacheable(const char *type)
{

    int rc = 0;

    if (/*cache_all ||*/ !slapi_UTF8CASECMP((char *)type, "nsrole")) {
        rc = 1;
    }

    return (rc);
}

/*
 * slapi_vattrcache_cache_all and slapi_vattrcache_cache_none
 * ----------------------------------------------------------
 * limited control for deciding whether to
 * cache anything, in reality controls whether
 * to cache cos attributes right now
 */
void
slapi_vattrcache_cache_all()
{
    cache_all = -1;
}

void
slapi_vattrcache_cache_none()
{
    cache_all = 0;
}

Slapi_PBlock *
slapi_vattr_get_pblock_from_context(vattr_context *c)
{
    if (c) {
        return c->pb;
    } else {
        return NULL;
    }
}

#ifdef VATTR_TEST_CODE

/* Prototype SP begins here */

/* Get value function */
int
vattr_basic_sp_get_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet **results, int *type_name_disposition, char **actual_type_name, int flags, int *buffer_flags, void *hint)
{
    Slapi_Value *value = NULL;
    *buffer_flags = 0;
    *actual_type_name = slapi_ch_strdup("dbtestattr");
    value = slapi_value_new_string("Hello Client");
    *results = slapi_ch_calloc(1, sizeof(Slapi_ValueSet));
    slapi_valueset_init(*results);
    slapi_valueset_add_value_ext(*results, value, SLAPI_VALUE_FLAG_PASSIN);
    return 0;
}

/* Compare value function */
int
vattr_basic_sp_compare_value(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int *result, int flags, void *hint)
{
    *result = 0;
    return 0;
}

int
vattr_basic_sp_list_types(vattr_sp_handle *handle, Slapi_Entry *e, vattr_type_list_context *type_context, int flags)
{
    static char *test_type_name = "dbtestattr";
    vattr_type_thang thang = {0};

    thang.type_name = test_type_name;
    thang.type_flags = 0;

    slapi_vattrspi_add_type(type_context, &thang, SLAPI_VIRTUALATTRS_REQUEST_POINTERS);
    return 0;
}

int
vattr_basic_sp_init()
{
    int ret = 0;
    vattr_sp_handle *my_handle = NULL;
    /* Register SP */
    ret = slapi_vattrspi_register(&my_handle, vattr_basic_sp_get_value, vattr_basic_sp_compare_value, vattr_basic_sp_list_types);
    if (ret) {
        slapd_nasty(sourcefile, 4, 0);
        return ret;
    }
    /* Register interest in some attribute over the entire tree */
    ret = slapi_vattrspi_regattr(my_handle, "dbtestattr", "", NULL);
    if (ret) {
        slapd_nasty(sourcefile, 5, 0);
        return ret;
    }
    /* Register interest in some attribute over the entire tree */
    ret = slapi_vattrspi_regattr(my_handle, "dbtestattr1", "", NULL);
    if (ret) {
        slapd_nasty(sourcefile, 5, 0);
        return ret;
    }
    /* Register interest in some attribute over the entire tree */
    ret = slapi_vattrspi_regattr(my_handle, "dbtestattr2", "", NULL);
    if (ret) {
        slapd_nasty(sourcefile, 5, 0);
        return ret;
    }
    /* Register interest in some attribute over the entire tree */
    ret = slapi_vattrspi_regattr(my_handle, "dbtestatt3r", "", NULL);
    if (ret) {
        slapd_nasty(sourcefile, 5, 0);
        return ret;
    }
    return ret;
}

/* What do we do on shutdown ? */
int
vattr_basic_sp_cleanup()
{
    return 0;
}

#endif
