/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2009 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

/*
 * Linked attributes plug-in
 */
#include "linked_attrs.h"


/*
 * Plug-in globals
 */
static PRCList *g_link_config = NULL;
static PRCList *g_managed_config_index = NULL;
static Slapi_RWLock *g_config_lock;

static void *_PluginID = NULL;
static char *_PluginDN = NULL;
int plugin_is_betxn = 0;

static Slapi_PluginDesc pdesc = {LINK_FEATURE_DESC,
                                 VENDOR,
                                 DS_PACKAGE_VERSION,
                                 LINK_PLUGIN_DESC};

/*
 * Plug-in management functions
 */
int linked_attrs_init(Slapi_PBlock *pb);
static int linked_attrs_start(Slapi_PBlock *pb);
static int linked_attrs_close(Slapi_PBlock *pb);
static int linked_attrs_postop_init(Slapi_PBlock *pb);
static int linked_attrs_internal_postop_init(Slapi_PBlock *pb);

/*
 * Operation callbacks (where the real work is done)
 */
static int linked_attrs_mod_post_op(Slapi_PBlock *pb);
static int linked_attrs_add_post_op(Slapi_PBlock *pb);
static int linked_attrs_del_post_op(Slapi_PBlock *pb);
static int linked_attrs_modrdn_post_op(Slapi_PBlock *pb);
static int linked_attrs_pre_op(Slapi_PBlock *pb, int modop);
static int linked_attrs_mod_pre_op(Slapi_PBlock *pb);
static int linked_attrs_add_pre_op(Slapi_PBlock *pb);

/*
 * Config cache management functions
 */
static int linked_attrs_load_config(void);
static void linked_attrs_delete_config(void);
static int linked_attrs_parse_config_entry(Slapi_Entry *e, int apply);
static void linked_attrs_insert_config_index(struct configEntry *entry);
static void linked_attrs_free_config_entry(struct configEntry **entry);

/*
 * helpers
 */
static char *linked_attrs_get_dn(Slapi_PBlock *pb);
static Slapi_DN *linked_attrs_get_sdn(Slapi_PBlock *pb);
static int linked_attrs_dn_is_config(char *dn);
static void linked_attrs_find_config(const char *dn, const char *type, struct configEntry **config);
static void linked_attrs_find_config_reverse(const char *dn,
                                             const char *type,
                                             struct configEntry **config);
static int linked_attrs_config_index_has_type(char *type);
static int linked_attrs_config_exists(struct configEntry *entry);
static int linked_attrs_config_exists_reverse(struct configEntry *entry);
static int linked_attrs_oktodo(Slapi_PBlock *pb);
void linked_attrs_load_array(Slapi_Value **array, Slapi_Attr *attr);
int linked_attrs_compare(const void *a, const void *b);
static int linked_attrs_add_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod);
static int linked_attrs_del_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod);
static int linked_attrs_replace_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod);
static int linked_attrs_mod_backpointers(Slapi_PBlock *pb, char *linkdn, char *type, char *scope, int modop, Slapi_ValueSet *targetvals);

/*
 * Config cache locking functions
 */
void
linked_attrs_read_lock()
{
    slapi_rwlock_rdlock(g_config_lock);
}

void
linked_attrs_write_lock()
{
    slapi_rwlock_wrlock(g_config_lock);
}

void
linked_attrs_unlock()
{
    slapi_rwlock_unlock(g_config_lock);
}


/*
 * Plugin identity functions
 */
void
linked_attrs_set_plugin_id(void *pluginID)
{
    _PluginID = pluginID;
}

void *
linked_attrs_get_plugin_id()
{
    return _PluginID;
}

void
linked_attrs_set_plugin_dn(const char *pluginDN)
{
    _PluginDN = (char *)pluginDN;
}

char *
linked_attrs_get_plugin_dn()
{
    return _PluginDN;
}

/*
 * Plug-in initialization functions
 */
int
linked_attrs_init(Slapi_PBlock *pb)
{
    int status = 0;
    char *plugin_identity = NULL;
    Slapi_Entry *plugin_entry = NULL;
    char *plugin_type = NULL;
    int preadd = SLAPI_PLUGIN_PRE_ADD_FN;
    int premod = SLAPI_PLUGIN_PRE_MODIFY_FN;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_init\n");

    if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
        plugin_entry &&
        (plugin_type = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-plugintype")) &&
        plugin_type && strstr(plugin_type, "betxn")) {
        plugin_is_betxn = 1;
        preadd = SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN;
        premod = SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN;
    }
    slapi_ch_free_string(&plugin_type);

    /* Store the plugin identity for later use.
     * Used for internal operations. */
    slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
    PR_ASSERT(plugin_identity);
    linked_attrs_set_plugin_id(plugin_identity);

    /* Register callbacks */
    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
                         SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
                         (void *)linked_attrs_start) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
                         (void *)linked_attrs_close) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
                         (void *)&pdesc) != 0 ||
        slapi_pblock_set(pb, premod, (void *)linked_attrs_mod_pre_op) != 0 ||
        slapi_pblock_set(pb, preadd, (void *)linked_attrs_add_pre_op) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_init - Failed to register plugin\n");
        status = -1;
    }

    if (!status && !plugin_is_betxn &&
        slapi_register_plugin("internalpostoperation",           /* op type */
                              1,                                 /* Enabled */
                              "linked_attrs_init",               /* this function desc */
                              linked_attrs_internal_postop_init, /* init func */
                              LINK_INT_POSTOP_DESC,              /* plugin desc */
                              NULL,                              /* ? */
                              plugin_identity                    /* access control */
                              )) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_init - Failed to register internalpostoperation plugin\n");
        status = -1;
    }

    if (!status) {
        plugin_type = "postoperation";
        if (plugin_is_betxn) {
            plugin_type = "betxnpostoperation";
        }
        if (slapi_register_plugin(plugin_type,              /* op type */
                                  1,                        /* Enabled */
                                  "linked_attrs_init",      /* this function desc */
                                  linked_attrs_postop_init, /* init func for post op */
                                  LINK_POSTOP_DESC,         /* plugin desc */
                                  NULL,                     /* ? */
                                  plugin_identity           /* access control */
                                  )) {
            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_init - Failed to register postop plugin\n");
            status = -1;
        }
    }

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_init\n");
    return status;
}

/* not used when using plugin as a betxn plugin - betxn plugins are called for both internal and external ops */
static int
linked_attrs_internal_postop_init(Slapi_PBlock *pb)
{
    int status = 0;

    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
                         SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
                         (void *)&pdesc) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN,
                         (void *)linked_attrs_add_post_op) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN,
                         (void *)linked_attrs_del_post_op) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN,
                         (void *)linked_attrs_mod_post_op) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN,
                         (void *)linked_attrs_modrdn_post_op) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_internal_postop_init - Failed to register plugin\n");
        status = -1;
    }

    return status;
}

static int
linked_attrs_postop_init(Slapi_PBlock *pb)
{
    int status = 0;
    int addfn = SLAPI_PLUGIN_POST_ADD_FN;
    int delfn = SLAPI_PLUGIN_POST_DELETE_FN;
    int modfn = SLAPI_PLUGIN_POST_MODIFY_FN;
    int mdnfn = SLAPI_PLUGIN_POST_MODRDN_FN;

    if (plugin_is_betxn) {
        addfn = SLAPI_PLUGIN_BE_TXN_POST_ADD_FN;
        delfn = SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN;
        modfn = SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN;
        mdnfn = SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN;
    }

    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
                         SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
                         (void *)&pdesc) != 0 ||
        slapi_pblock_set(pb, addfn, (void *)linked_attrs_add_post_op) != 0 ||
        slapi_pblock_set(pb, delfn, (void *)linked_attrs_del_post_op) != 0 ||
        slapi_pblock_set(pb, modfn, (void *)linked_attrs_mod_post_op) != 0 ||
        slapi_pblock_set(pb, mdnfn, (void *)linked_attrs_modrdn_post_op) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_postop_init - Failed to register plugin\n");
        status = -1;
    }

    return status;
}


/*
 * linked_attrs_start()
 *
 * Creates config lock and loads config cache.
 */
static int
linked_attrs_start(Slapi_PBlock *pb)
{
    Slapi_DN *plugindn = NULL;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_start\n");

    g_config_lock = slapi_new_rwlock();

    if (!g_config_lock) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_start - Lock creation failed\n");

        return -1;
    }

    /*
     * Get the plug-in target dn from the system
     * and store it for future use. */
    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &plugindn);
    if (NULL == plugindn || 0 == slapi_sdn_get_ndn_len(plugindn)) {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_start - Unable to retrieve plugin dn\n");
        return -1;
    }

    linked_attrs_set_plugin_dn(slapi_sdn_get_dn(plugindn));

    /*
     * Load the config cache
     */
    g_link_config = (PRCList *)slapi_ch_calloc(1, sizeof(struct configEntry));
    PR_INIT_CLIST(g_link_config);
    g_managed_config_index = (PRCList *)slapi_ch_calloc(1, sizeof(struct configIndex));
    PR_INIT_CLIST(g_managed_config_index);

    if (linked_attrs_load_config() != 0) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_start - Unable to load plug-in configuration\n");
        return -1;
    }

    /*
     * Register our task callback
     */
    slapi_plugin_task_register_handler("fixup linked attributes", linked_attrs_fixup_task_add, pb);

    slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                  "linked_attrs_start: ready for service\n");
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_start\n");

    return 0;
}

/*
 * linked_attrs_close()
 *
 * Cleans up the config cache.
 */
static int
linked_attrs_close(Slapi_PBlock *pb __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_close\n");

    slapi_plugin_task_unregister_handler("fixup linked attributes", linked_attrs_fixup_task_add);

    linked_attrs_delete_config();
    slapi_destroy_rwlock(g_config_lock);
    g_config_lock = NULL;
    slapi_ch_free((void **)&g_link_config);
    slapi_ch_free((void **)&g_managed_config_index);

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_close\n");

    return 0;
}

PRCList *
linked_attrs_get_config()
{
    return g_link_config;
}

/*
 * config looks like this
 * - cn=myplugin
 * --- cn=manager link
 * --- cn=owner link
 * --- cn=etc
 */
static int
linked_attrs_load_config(void)
{
    int status = 0;
    int result;
    int i;
    Slapi_PBlock *search_pb;
    Slapi_Entry **entries = NULL;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_load_config\n");

    /* Clear out any old config. */
    linked_attrs_write_lock();
    linked_attrs_delete_config();

    /* Find the config entries beneath our plugin entry. */
    search_pb = slapi_pblock_new();
    slapi_search_internal_set_pb(search_pb, linked_attrs_get_plugin_dn(),
                                 LDAP_SCOPE_SUBTREE, "objectclass=*",
                                 NULL, 0, NULL, NULL, linked_attrs_get_plugin_id(), 0);
    slapi_search_internal_pb(search_pb);
    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);

    if (LDAP_SUCCESS != result) {
        status = -1;
        goto cleanup;
    }

    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
                     &entries);
    if (NULL == entries || NULL == entries[0]) {
        /* If there are no config entries, we're done. */
        goto cleanup;
    }

    /* Loop through all of the entries we found and parse them. */
    for (i = 0; (entries[i] != NULL); i++) {
        /* We don't care about the status here because we may have
         * some invalid config entries, but we just want to continue
         * looking for valid ones. */
        linked_attrs_parse_config_entry(entries[i], 1);
    }

cleanup:
    slapi_free_search_results_internal(search_pb);
    slapi_pblock_destroy(search_pb);
    linked_attrs_unlock();
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_load_config\n");

    return status;
}

/*
 * linked_attrs_parse_config_entry()
 *
 * Parses a single config entry.  If apply is non-zero, then
 * we will load and start using the new config.  You can simply
 * validate config without making any changes by setting apply
 * to 0.
 *
 * Returns 0 if the entry is valid and -1 if it is invalid.
 */
static int
linked_attrs_parse_config_entry(Slapi_Entry *e, int apply)
{
    char *value;
    struct configEntry *entry = NULL;
    struct configEntry *config_entry;
    PRCList *list;
    int entry_added = 0;
    int ret = 0;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_parse_config_entry\n");

    /* If this is the main plug-in
     * config entry, just bail. */
    if (strcasecmp(linked_attrs_get_plugin_dn(), slapi_entry_get_ndn(e)) == 0) {
        ret = -1;
        goto bail;
    }

    entry = (struct configEntry *)slapi_ch_calloc(1, sizeof(struct configEntry));
    if (NULL == entry) {
        ret = -1;
        goto bail;
    }

    value = slapi_entry_get_ndn(e);
    if (value) {
        entry->dn = slapi_ch_strdup(value);
    } else {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - Error "
                      "reading dn from config entry\n");
        ret = -1;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                  "----------> dn [%s]\n", entry->dn);

    value = slapi_entry_attr_get_charptr(e, LINK_LINK_TYPE);
    if (value) {
        int not_dn_syntax = 0;
        char *syntaxoid = NULL;
        Slapi_Attr *attr = slapi_attr_new();

        /* Set this first so we free it if we encounter an error */
        entry->linktype = value;

        /* Gather some information about this attribute. */
        slapi_attr_init(attr, value);
        slapi_attr_get_syntax_oid_copy(attr, &syntaxoid);
        not_dn_syntax = strcmp(syntaxoid, DN_SYNTAX_OID);
        slapi_ch_free_string(&syntaxoid);
        slapi_attr_free(&attr);

        /* Check if the link type's syntax is Distinguished Name.
         * We only treat this as a warning. */
        if (not_dn_syntax) {
            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_parse_config_entry - The %s config "
                          "setting must be set to an attribute with the "
                          "Distinguished Name syntax for linked attribute "
                          "pair \"%s\" attribute \"%s\".\n",
                          LINK_LINK_TYPE, entry->dn, value);
        }
    } else {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - The %s config "
                      "setting is required for linked attribute pair \"%s\".\n",
                      LINK_LINK_TYPE, entry->dn);
        ret = -1;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                  "linked_attrs_parse_config_entry - %s [%s]\n", LINK_LINK_TYPE, entry->linktype);

    value = slapi_entry_attr_get_charptr(e, LINK_MANAGED_TYPE);
    if (value) {
        int single_valued = 0;
        int not_dn_syntax = 0;
        char *syntaxoid = NULL;
        Slapi_Attr *attr = slapi_attr_new();

        /* Set this first so we free it if we encounter an error */
        entry->managedtype = value;

        /* Gather some information about this attribute. */
        slapi_attr_init(attr, value);
        slapi_attr_get_syntax_oid_copy(attr, &syntaxoid);
        not_dn_syntax = strcmp(syntaxoid, DN_SYNTAX_OID);
        single_valued = slapi_attr_flag_is_set(attr, SLAPI_ATTR_FLAG_SINGLE);
        slapi_ch_free_string(&syntaxoid);
        slapi_attr_free(&attr);

        /* Ensure that the managed type is a multi-valued attribute. */
        if (single_valued) {
            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_parse_config_entry - The %s config "
                          "setting must be set to a multi-valued attribute "
                          "for linked attribute pair \"%s\".\n",
                          LINK_MANAGED_TYPE, entry->dn);
            ret = -1;
            goto bail;
            /* Check if the link type's syntax is Distinguished Name.
         * We only treat this as a warning. */
        } else if (not_dn_syntax) {
            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_parse_config_entry - The %s config "
                          "setting must be set to an attribute with the "
                          "Distinguished Name syntax for linked attribute "
                          "pair \"%s\".\n",
                          LINK_MANAGED_TYPE, entry->dn);
        }
    } else {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - The %s config "
                      "setting is required for linked attribute pair \"%s\".\n",
                      LINK_MANAGED_TYPE, entry->dn);
        ret = -1;
        goto bail;
    }

    slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                  "linked_attrs_parse_config_entry - %s [%s]\n", LINK_MANAGED_TYPE,
                  entry->managedtype);

    /* A scope is not required.  No scope means it
     * applies to any part of the DIT. */
    value = slapi_entry_attr_get_charptr(e, LINK_SCOPE);
    if (value) {
        entry->scope = value;
    }

    slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                  "----------> %s [%s]\n", LINK_SCOPE,
                  entry->scope ? entry->scope : "NULL");

    /* Check if config already exists for
     * the link type at the same scope. */
    if (linked_attrs_config_exists(entry)) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - A config "
                      "entry for the link attribute %s already "
                      "exists at a scope of \"%s\".\n",
                      entry->linktype,
                      entry->scope);
        ret = -1;
        goto bail;
    }

    /* Check if config already exists for
     * the managed type at the same scope. */
    if (linked_attrs_config_exists_reverse(entry)) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - A config "
                      "entry for the managed attribute %s already "
                      "exists at a scope of \"%s\".\n",
                      entry->managedtype,
                      entry->scope);
        ret = -1;
        goto bail;
    }

    /* If we were only called to validate config, we can
     * just bail out before applying the config changes */
    if (apply == 0) {
        goto bail;
    }

    /* Create a lock for this attribute pair. */
    entry->lock = slapi_new_mutex();
    if (!entry->lock) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - Unable to create "
                      "lock for linked attribute pair \"%s\".\n",
                      entry->dn);
        ret = -1;
        goto bail;
    }

    /* Add the entry to the list.  We group by link type. We
     * also maintain a reverse list grouped by managed type. */
    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
        list = PR_LIST_HEAD(g_link_config);
        while (list != g_link_config) {
            config_entry = (struct configEntry *)list;

            /* See if the types match.  We want to group
             * entries for the same link type together. */
            if (slapi_attr_type_cmp(config_entry->linktype, entry->linktype, 1) == 0) {
                PR_INSERT_BEFORE(&(entry->list), list);
                slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_parse_config_entry - store [%s] before [%s] \n", entry->dn,
                              config_entry->dn);

                /* add to managed type index */
                linked_attrs_insert_config_index(entry);

                entry_added = 1;
                break;
            }

            list = PR_NEXT_LINK(list);

            if (g_link_config == list) {
                /* add to tail */
                PR_INSERT_BEFORE(&(entry->list), list);
                slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_parse_config_entry - store [%s] at tail\n", entry->dn);

                /* add to managed type index */
                linked_attrs_insert_config_index(entry);

                entry_added = 1;
                break;
            }
        }
    } else {
        /* first entry */
        PR_INSERT_LINK(&(entry->list), g_link_config);
        slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_parse_config_entry - store [%s] at head \n", entry->dn);

        /* add to managed type index */
        linked_attrs_insert_config_index(entry);

        entry_added = 1;
    }

bail:
    if (0 == entry_added) {
        /* Don't log error if we weren't asked to apply config */
        if ((apply != 0) && (entry != NULL)) {
            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_parse_config_entry - Invalid config entry "
                          "[%s] skipped\n",
                          entry->dn);
        }
        linked_attrs_free_config_entry(&entry);
    } else {
        ret = 0;
    }

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_parse_config_entry\n");

    return ret;
}

/*
 * linked_attrs_insert_config_index()
 *
 * Adds an entry to the ordered config index.  We maintain
 * an list of pointers to the cached config entries that is
 * grouped by managed type. We use this index to find the
 * appropriate config entry when given a backpointer.  This
 * is useful for the case where an entry with backpointers
 * is renamed and we need to updated the forward link.
 */
static void
linked_attrs_insert_config_index(struct configEntry *entry)
{
    struct configEntry *config_entry = NULL;
    struct configIndex *index_entry = NULL;
    PRCList *list = PR_LIST_HEAD(g_managed_config_index);
    int inserted = 0;

    index_entry = (struct configIndex *)slapi_ch_calloc(1, sizeof(struct configIndex));
    index_entry->config = entry;

    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        while (list != g_managed_config_index) {
            config_entry = ((struct configIndex *)list)->config;

            /* See if the types match. */
            if (slapi_attr_type_cmp(config_entry->managedtype, entry->managedtype, 1) == 0) {
                PR_INSERT_BEFORE(&(index_entry->list), list);
                slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_insert_config_index - store [%s] before [%s] \n", entry->dn,
                              config_entry->dn);
                inserted = 1;
                break;
            }

            list = PR_NEXT_LINK(list);

            if (g_managed_config_index == list) {
                /* add to tail */
                PR_INSERT_BEFORE(&(index_entry->list), list);
                slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_insert_config_index - store [%s] at tail\n", entry->dn);
                inserted = 1;
                break;
            }
        }
    } else {
        /* first entry */
        slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_insert_config_index - store [%s] at head \n", entry->dn);
        PR_INSERT_LINK(&(index_entry->list), g_managed_config_index);
        inserted = 1;
    }
    if (!inserted) {
        slapi_ch_free((void **)&index_entry);
    }
}

static void
linked_attrs_free_config_entry(struct configEntry **entry)
{
    struct configEntry *e = *entry;

    if (e == NULL)
        return;

    if (e->dn) {
        slapi_log_err(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_free_config_entry - freeing config entry [%s]\n", e->dn);
        slapi_ch_free_string(&e->dn);
    }

    if (e->linktype)
        slapi_ch_free_string(&e->linktype);

    if (e->managedtype)
        slapi_ch_free_string(&e->managedtype);

    if (e->scope)
        slapi_ch_free_string(&e->scope);

    if (e->lock)
        slapi_destroy_mutex(e->lock);

    slapi_ch_free((void **)entry);
}

static void
linked_attrs_delete_configEntry(PRCList *entry)
{
    PR_REMOVE_LINK(entry);
    linked_attrs_free_config_entry((struct configEntry **)&entry);
}

static void
linked_attrs_delete_config(void)
{
    PRCList *list;

    /* Delete the config cache. */
    while (!PR_CLIST_IS_EMPTY(g_link_config)) {
        list = PR_LIST_HEAD(g_link_config);
        linked_attrs_delete_configEntry(list);
    }

    /* Delete the reverse index. */
    while (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        list = PR_LIST_HEAD(g_managed_config_index);
        PR_REMOVE_LINK(list);
        slapi_ch_free((void **)&list);
    }

    return;
}


/*
 * Helper functions
 */
static char *
linked_attrs_get_dn(Slapi_PBlock *pb)
{
    const char *dn = 0;
    Slapi_DN *sdn = NULL;
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_get_dn\n");

    if (slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn)) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_get_dn - Failed to get dn of changed entry");
        goto bail;
    }
    dn = slapi_sdn_get_dn(sdn);

bail:
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_get_dn\n");

    return (char *)dn;
}

static Slapi_DN *
linked_attrs_get_sdn(Slapi_PBlock *pb)
{
    Slapi_DN *sdn = 0;
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_get_sdn\n");
    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_get_sdn\n");

    return sdn;
}

/*
 * linked_attrs_dn_is_config()
 *
 * Checks if dn is a linked attribute config entry.
 */
static int
linked_attrs_dn_is_config(char *dn)
{
    int ret = 0;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_dn_is_config\n");

    /* Return 1 if the passed in dn is a child of the main
     * plugin config entry. */
    if (slapi_dn_issuffix(dn, linked_attrs_get_plugin_dn()) &&
        strcasecmp(dn, linked_attrs_get_plugin_dn())) {
        ret = 1;
    }

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_dn_is_config\n");

    return ret;
}

/*
 * linked_attrs_find_config()
 *
 * Finds the appropriate config entry for a given dn and
 * link type.  A read lock must be held on the config
 * before calling this function.  The configEntry that is
 * returned is a pointer to the actual config entry in
 * the config cache.  It should not be modified in any
 * way.  The read lock should not be released until you
 * are finished with the config entry that is returned.

 * Returns NULL if no applicable config entry is found.
 */
static void
linked_attrs_find_config(const char *dn,
                         const char *type,
                         struct configEntry **config)
{
    int found_type = 0;
    PRCList *list = NULL;

    *config = NULL;

    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
        list = PR_LIST_HEAD(g_link_config);
        while (list != g_link_config) {
            if (slapi_attr_type_cmp(((struct configEntry *)list)->linktype,
                                    type, 1) == 0) {
                /* Set a flag indicating that we found a config entry
                 * for this type. We use this flag so we can stop
                 * processing early if we don't find a matching scope. */
                found_type = 1;

                /* Check if the dn is in the scope of this config
                 * entry.  If the config entry doesn't have a scope
                 * (global), consider it a match.  If we have a match,
                 * we can stop processing the config. */
                if ((((struct configEntry *)list)->scope == NULL) ||
                    (slapi_dn_issuffix(dn, ((struct configEntry *)list)->scope))) {
                    *config = (struct configEntry *)list;
                    break;
                }
            } else {
                /* If flag is set, we're done.  We have configured links
                 * for this type, but none of the scopes match. */
                if (found_type) {
                    break;
                }
            }

            list = PR_NEXT_LINK(list);
        }
    }
}

/*
 * linked_attrs_find_config_reverse()
 *
 * Finds the appropriate config entry for a given dn and
 * managed type.  A read lock must be held on the config
 * before calling this function.  The configEntry that is
 * returned is a pointer to the actual config entry in
 * the config cache.  It should not be modified in any
 * way.  The read lock should not be released until you
 * are finished with the config entry that is returned.

 * Returns NULL if no applicable config entry is found.
 */
static void
linked_attrs_find_config_reverse(const char *dn,
                                 const char *type,
                                 struct configEntry **config)
{
    int found_type = 0;
    PRCList *list = NULL;

    *config = NULL;

    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        list = PR_LIST_HEAD(g_managed_config_index);
        while (list != g_managed_config_index) {
            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
                                    type, 1) == 0) {
                /* Set a flag indicating that we found a config entry
                 * for this type. We use this flag so we can stop
                 * processing early if we don't find a matching scope. */
                found_type = 1;

                /* Check if the dn is in the scope of this config
                 * entry.  If the config entry doesn't have a scope
                 * (global), consider it a match.  If we have a match,
                 * we can stop processing the config. */
                if ((((struct configIndex *)list)->config->scope == NULL) ||
                    (slapi_dn_issuffix(dn, ((struct configIndex *)list)->config->scope))) {
                    *config = ((struct configIndex *)list)->config;
                    break;
                }
            } else {
                /* If flag is set, we're done.  We have configured links
                 * for this type, but none of the scopes match. */
                if (found_type) {
                    break;
                }
            }

            list = PR_NEXT_LINK(list);
        }
    }
}

/*
 * linked_attrs_config_index_has_type()
 *
 * Returns 1 if a config entry exists with the passed
 * in managed type.
 *
 * A read lock on the config must be held before calling
 * this function.
 */
static int
linked_attrs_config_index_has_type(char *type)
{
    int rc = 0;
    PRCList *list = NULL;

    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        list = PR_LIST_HEAD(g_managed_config_index);
        while (list != g_managed_config_index) {
            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
                                    type, 1) == 0) {
                rc = 1;
                break;
            }

            list = PR_NEXT_LINK(list);
        }
    }

    return rc;
}

/*
 * linked_attrs_config_exists()
 *
 * Returns 1 if a config entry exists in the cache
 * already for the given link type at the given scope.
 * This will detect if the cached config entry is really
 * the same one as the passed in entry by comparing the
 * dn of the config entry.  We will still return 0 in
 * this case as it's one and the same config entry. We
 * really want to use this to prevent multiple config
 * entries for the same link type at the same scope.
 *
 * A read lock on the config must be held before calling
 * this function.
 */
static int
linked_attrs_config_exists(struct configEntry *entry)
{
    int rc = 0;
    int found_type = 0;
    PRCList *list = NULL;

    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
        list = PR_LIST_HEAD(g_link_config);
        while (list != g_link_config) {
            if (slapi_attr_type_cmp(((struct configEntry *)list)->linktype,
                                    entry->linktype, 1) == 0) {
                found_type = 1;
                /* We don't allow nested config for the same type.  We
                 * need to check for nesting in both directions here.
                 * If no scope is set, we consider the entry global. */
                if ((((struct configEntry *)list)->scope == NULL) ||
                    slapi_dn_issuffix(entry->scope, ((struct configEntry *)list)->scope) ||
                    slapi_dn_issuffix(((struct configEntry *)list)->scope, entry->scope)) {
                    /* Make sure that this isn't the same exact entry
                     * in the list already.  This can happen if a config
                     * entry is being modified.  Both of these were already
                     * normalized when the config struct was filled in. */
                    if (strcasecmp(entry->dn, ((struct configEntry *)list)->dn) != 0) {
                        rc = 1;
                        break;
                    }
                }
            } else {
                if (found_type) {
                    /* Since the list is sorted by link type, we
                     * are finished if we get here since we found
                     * the type but didn't match the scope. */
                    break;
                }
            }

            list = PR_NEXT_LINK(list);
        }
    }

    return rc;
}

/*
 * linked_attrs_config_exists_reverse()
 *
 * Returns 1 if a config entry exists in the cache
 * already for the given managed type at the given scope.
 * This will detect if the cached config entry is really
 * the same one as the passed in entry by comparing the
 * dn of the config entry.  We will still return 0 in
 * this case as it's one and the same config entry. We
 * really want to use this to prevent multiple config
 * entries for the same managed type at the same scope.
 *
 * A read lock on the config must be held before calling
 * this function.
 */
static int
linked_attrs_config_exists_reverse(struct configEntry *entry)
{
    int rc = 0;
    int found_type = 0;
    PRCList *list = NULL;

    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        list = PR_LIST_HEAD(g_managed_config_index);
        while (list != g_managed_config_index) {
            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
                                    entry->managedtype, 1) == 0) {
                found_type = 1;
                /* We don't allow nested config for the same type.  We
                 * need to check for nesting in both directions here. */
                if ((((struct configIndex *)list)->config->scope == NULL) ||
                    slapi_dn_issuffix(entry->scope,
                                      ((struct configIndex *)list)->config->scope) ||
                    slapi_dn_issuffix(((struct configIndex *)list)->config->scope,
                                      entry->scope)) {
                    /* Make sure that this isn't the same exact entry
                     * in the list already.  This can happen if a config
                     * entry is being modified.  Both of these were already
                     * normalized when the config struct was filled in. */
                    if (strcasecmp(entry->dn, ((struct configIndex *)list)->config->dn) != 0) {
                        rc = 1;
                        break;
                    }
                }
            } else {
                if (found_type) {
                    /* Since the list is sorted by link type, we
                     * are finished if we get here since we found
                     * the type but didn't match the scope. */
                    break;
                }
            }

            list = PR_NEXT_LINK(list);
        }
    }

    return rc;
}

/*
 * linked_attrs_oktodo()
 *
 * Check if we want to process this operation.  We need to be
 * sure that the operation succeeded.  We also respond to replicated
 * ops so we don't test for that.  This does require that the managed
 * attributes not be replicated.
 */
static int
linked_attrs_oktodo(Slapi_PBlock *pb)
{
    int ret = 1;
    int oprc = 0;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_oktodo\n");

    if (slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_oktodo - Could not get parameters\n");
        ret = -1;
    }

    /* This plugin should only execute if the operation succeeded. */
    if (oprc != 0) {
        ret = 0;
    }

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_oktodo\n");

    return ret;
}

/* linked_attrs_load_array()
 *
 * put attribute values in array structure
 */
void
linked_attrs_load_array(Slapi_Value **array, Slapi_Attr *attr)
{
    Slapi_Value *val = 0;
    int hint = slapi_attr_first_value(attr, &val);

    while (val) {
        *array = val;
        array++;
        hint = slapi_attr_next_value(attr, hint, &val);
    }
}

/* linked_attrs_compare()
 *
 * Compare two attr values using the DN syntax.
 */
int
linked_attrs_compare(const void *a, const void *b)
{
    Slapi_Value *val1;
    Slapi_Value *val2;
    Slapi_Attr *linkattr;
    int rc = 0;

    if (a == NULL && b != NULL) {
        return 1;
    } else if (a != NULL && b == NULL) {
        return -1;
    } else if (a == NULL && b == NULL) {
        return 0;
    }
    val1 = *((Slapi_Value **)a);
    val2 = *((Slapi_Value **)b);
    linkattr = slapi_attr_new();

    slapi_attr_init(linkattr, "distinguishedName");

    rc = slapi_attr_value_cmp(linkattr,
                              slapi_value_get_berval(val1),
                              slapi_value_get_berval(val2));

    slapi_attr_free(&linkattr);

    return rc;
}

/*
 * linked_attrs_add_backpointers()
 *
 * Adds backpointers pointing to dn to the entries referred to
 * by the values in smod.
 */
static int
linked_attrs_add_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod)
{
    Slapi_ValueSet *vals = slapi_valueset_new();
    int rc = LDAP_SUCCESS;

    slapi_valueset_set_from_smod(vals, smod);
    rc = linked_attrs_mod_backpointers(pb, linkdn, config->managedtype, config->scope,
                                       LDAP_MOD_ADD, vals);

    slapi_valueset_free(vals);
    return rc;
}

/*
 * linked_attrs_del_backpointers()
 *
 * Remove backpointers pointing to linkdn in the entries referred
 * to by the values in smod.
 */
static int
linked_attrs_del_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod)
{
    Slapi_ValueSet *vals = NULL;
    int rc = LDAP_SUCCESS;

    /* If no values are listed in the smod, we need to get
     * a list of all of the values that were deleted by
     * looking at the pre-op copy of the entry. */
    if (slapi_mod_get_num_values(smod) == 0) {
        Slapi_Entry *pre_e = NULL;
        Slapi_Attr *pre_attr = NULL;

        slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre_e);
        slapi_entry_attr_find(pre_e, config->linktype, &pre_attr);
        slapi_attr_get_valueset(pre_attr, &vals);
    } else {
        vals = slapi_valueset_new();
        slapi_valueset_set_from_smod(vals, smod);
    }

    rc = linked_attrs_mod_backpointers(pb, linkdn, config->managedtype, config->scope,
                                       LDAP_MOD_DELETE, vals);
    slapi_valueset_free(vals);

    return rc;
}

/*
 * linked_attrs_replace_backpointers()
 *
 * Remove backpointers pointing to linkdn from the entries
 * whose values were deleted in smod and add backpointers
 * for any new values that were added as a part of the
 * replace operation.
 */
static int
linked_attrs_replace_backpointers(Slapi_PBlock *pb, char *linkdn, struct configEntry *config, Slapi_Mod *smod __attribute__((unused)))
{
    Slapi_Entry *pre_e = NULL;
    Slapi_Entry *post_e = NULL;
    Slapi_Attr *pre_attr = 0;
    Slapi_Attr *post_attr = 0;
    int rc = LDAP_SUCCESS;

    /* Get the pre and post copy of the entry to see
     * what values have been added and removed. */
    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre_e);
    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &post_e);

    if (pre_e && post_e) {
        slapi_entry_attr_find(pre_e, config->linktype, &pre_attr);
        slapi_entry_attr_find(post_e, config->linktype, &post_attr);
    }

    if (pre_attr || post_attr) {
        int pre_total = 0;
        int post_total = 0;
        Slapi_Value **pre_array = 0;
        Slapi_Value **post_array = 0;
        int pre_index = 0;
        int post_index = 0;
        Slapi_ValueSet *addvals = NULL;
        Slapi_ValueSet *delvals = NULL;

        /* create arrays of values */
        if (pre_attr) {
            slapi_attr_get_numvalues(pre_attr, &pre_total);
        }

        if (post_attr) {
            slapi_attr_get_numvalues(post_attr, &post_total);
        }

        if (pre_total) {
            pre_array = (Slapi_Value **)slapi_ch_malloc(sizeof(Slapi_Value *) * pre_total);
            linked_attrs_load_array(pre_array, pre_attr);
            qsort(pre_array, pre_total, sizeof(Slapi_Value *), linked_attrs_compare);
        }

        if (post_total) {
            post_array = (Slapi_Value **)slapi_ch_malloc(sizeof(Slapi_Value *) * post_total);
            linked_attrs_load_array(post_array, post_attr);
            qsort(post_array, post_total, sizeof(Slapi_Value *), linked_attrs_compare);
        }

        /* Work through arrays, following these rules:
         *   - in pre, in post, do nothing
         *   - in pre, not in post, delete from entry
         *   - not in pre, in post, add to entry
         */
        while (pre_index < pre_total || post_index < post_total) {
            if (pre_index == pre_total) {
                /* add the rest of post */
                if (addvals == NULL) {
                    addvals = slapi_valueset_new();
                }

                slapi_valueset_add_value(addvals, post_array[post_index]);
                post_index++;
            } else if (post_index == post_total) {
                /* delete the rest of pre */
                if (delvals == NULL) {
                    delvals = slapi_valueset_new();
                }

                slapi_valueset_add_value(delvals, pre_array[pre_index]);
                pre_index++;
            } else {
                /* decide what to do */
                int cmp = linked_attrs_compare(&(pre_array[pre_index]),
                                               &(post_array[post_index]));

                if (cmp < 0) {
                    /* delete pre array */
                    if (delvals == NULL) {
                        delvals = slapi_valueset_new();
                    }

                    slapi_valueset_add_value(delvals, pre_array[pre_index]);
                    pre_index++;
                } else if (cmp > 0) {
                    /* add post array */
                    if (addvals == NULL) {
                        addvals = slapi_valueset_new();
                    }

                    slapi_valueset_add_value(addvals, post_array[post_index]);
                    post_index++;
                } else {
                    /* do nothing, advance */
                    pre_index++;
                    post_index++;
                }
            }
        }

        /* Perform the actual updates to the target entries. */
        if (delvals) {
            rc = linked_attrs_mod_backpointers(pb, linkdn, config->managedtype,
                                               config->scope, LDAP_MOD_DELETE, delvals);
            slapi_valueset_free(delvals);
        }

        if (rc == LDAP_SUCCESS && addvals) {
            rc = linked_attrs_mod_backpointers(pb, linkdn, config->managedtype,
                                               config->scope, LDAP_MOD_ADD, addvals);
            slapi_valueset_free(addvals);
        }

        slapi_ch_free((void **)&pre_array);
        slapi_ch_free((void **)&post_array);
    }

    return rc;
}

/*
 * linked_attrs_mod_backpointers()
 *
 * Performs backpointer management.
 */
static int
linked_attrs_mod_backpointers(Slapi_PBlock *pb, char *linkdn, char *type, char *scope, int modop, Slapi_ValueSet *targetvals)
{
    char *val[2];
    int i = 0;
    Slapi_PBlock *mod_pb = slapi_pblock_new();
    LDAPMod mod;
    LDAPMod *mods[2];
    Slapi_Value *targetval = NULL;
    int rc = LDAP_SUCCESS;

    /* Setup the modify operation.  Only the target will
     * change, so we only need to do this once. */
    val[0] = linkdn;
    val[1] = 0;

    mod.mod_op = modop;
    mod.mod_type = type;
    mod.mod_values = val;

    mods[0] = &mod;
    mods[1] = 0;

    i = slapi_valueset_first_value(targetvals, &targetval);
    while (targetval) {
        int perform_update = 0;
        const char *targetdn = slapi_value_get_string(targetval);
        Slapi_DN *targetsdn = slapi_sdn_new_dn_byref(targetdn);

        /* If we have a scope, only update the target if it is within
         * the scope.  If we don't have a scope, only update the target
         * if it is in the same backend as the linkdn. */
        if (scope) {
            perform_update = slapi_dn_issuffix(targetdn, scope);
        } else {
            Slapi_Backend *be = NULL;
            Slapi_DN *linksdn = slapi_sdn_new_normdn_byref(linkdn);

            if ((be = slapi_be_select(linksdn))) {
                perform_update = slapi_sdn_issuffix(targetsdn, slapi_be_getsuffix(be, 0));
            }

            slapi_sdn_free(&linksdn);
        }

        if (perform_update) {
            slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                          "linked_attrs_mod_backpointers - %s backpointer (%s) in entry (%s)\n",
                          (modop == LDAP_MOD_ADD) ? "Adding" : "Removing",
                          linkdn, targetdn);

            /* Perform the modify operation. */
            slapi_modify_internal_set_pb_ext(mod_pb, targetsdn, mods, 0, 0,
                                             linked_attrs_get_plugin_id(), 0);
            slapi_modify_internal_pb(mod_pb);
            slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
            if (((LDAP_MOD_DELETE == modop) && (LDAP_NO_SUCH_OBJECT == rc)) ||
                ((LDAP_MOD_ADD == modop) && (LDAP_TYPE_OR_VALUE_EXISTS == rc))) {
                /*
                 * We should ignore LDAP_NO_SUCH_OBJECT and LDAP_TYPE_OR_VALUE_EXISTS
                 * to support the case:
                 *   target entry was renamed and the linktype value is being adjusted.
                 */
                rc = LDAP_SUCCESS;
            } else if (rc != LDAP_SUCCESS) {
                char *err_msg = NULL;

                slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_mod_backpointers - Failed to update link to target entry (%s) error %d",
                              targetdn, rc);
                err_msg = PR_smprintf("linked_attrs_mod_backpointers - Failed to update "
                                      "link to target entry (%s) error %d",
                                      targetdn, rc);
                slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM, "%s\n", err_msg);
                slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, err_msg);
                PR_smprintf_free(err_msg);
                slapi_sdn_free(&targetsdn);
                if (i > 0) {
                    int j;
                    Slapi_ValueSet *undoVals = slapi_valueset_new();
                    /* undo 0..i-1 */
                    j = slapi_valueset_first_value(targetvals, &targetval);
                    do {
                        slapi_valueset_add_value(undoVals, targetval);
                        j = slapi_valueset_next_value(targetvals, j, &targetval);
                    } while (j < i);
                    if (LDAP_MOD_DELETE == modop) {
                        modop = LDAP_MOD_ADD;
                    } else {
                        modop = LDAP_MOD_DELETE;
                    }
                    rc = linked_attrs_mod_backpointers(pb, linkdn, type, scope, modop, undoVals);
                    slapi_valueset_free(undoVals);
                }
                rc = LDAP_UNWILLING_TO_PERFORM;
                break;
            }
            /* Initialize the pblock so we can reuse it. */
            slapi_pblock_init(mod_pb);
        }
        slapi_sdn_free(&targetsdn);

        i = slapi_valueset_next_value(targetvals, i, &targetval);
    }

    slapi_pblock_destroy(mod_pb);

    return rc;
}


/*
 * Operation callback functions
 */

/*
 * linked_attrs_pre_op()
 *
 * Checks if an operation is modifying the linked
 * attribute config and validates the config changes.
 */
static int
linked_attrs_pre_op(Slapi_PBlock *pb, int modop)
{
    char *dn = 0;
    Slapi_Entry *e = 0;
    LDAPMod **mods = NULL;
    int free_entry = 0;
    char *errstr = NULL;
    int ret = SLAPI_PLUGIN_SUCCESS;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_pre_op\n");

    if (0 == (dn = linked_attrs_get_dn(pb)))
        goto bail;

    if (linked_attrs_dn_is_config(dn)) {
        /* Validate config changes, but don't apply them.
         * This allows us to reject invalid config changes
         * here at the pre-op stage.  Applying the config
         * needs to be done at the post-op stage. */

        if (LDAP_CHANGETYPE_ADD == modop) {
            slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
        } else {
            /* Fetch the entry being modified so we can
             * create the resulting entry for validation. */
            /* int free_sdn = 0; */
            Slapi_DN *tmp_dn = linked_attrs_get_sdn(pb);
            if (tmp_dn) {
                slapi_search_internal_get_entry(tmp_dn, 0, &e, linked_attrs_get_plugin_id());
                free_entry = 1;
            }

            /* If the entry doesn't exist, just bail and
             * let the server handle it. */
            if (e == NULL) {
                goto bail;
            }

            /* Grab the mods. */
            slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
            /* Apply the  mods to create the resulting entry. */
            if (mods && (slapi_entry_apply_mods(e, mods) != LDAP_SUCCESS)) {
                /* The mods don't apply cleanly, so we just let this op go
                 * to let the main server handle it. */
                goto bail;
            }
        }

        if (linked_attrs_parse_config_entry(e, 0) != 0) {
            /* Refuse the operation if config parsing failed. */
            ret = LDAP_UNWILLING_TO_PERFORM;
            if (LDAP_CHANGETYPE_ADD == modop) {
                errstr = slapi_ch_smprintf("Not a valid linked attribute configuration entry.");
            } else {
                errstr = slapi_ch_smprintf("Changes result in an invalid "
                                           "linked attribute configuration.");
            }
        }
    }

bail:
    if (free_entry && e)
        slapi_entry_free(e);

    if (ret) {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_pre_op - Operation failure [%d]\n", ret);
        slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
        slapi_ch_free((void **)&errstr);
        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ret);
        ret = SLAPI_PLUGIN_FAILURE;
    }

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_pre_op\n");

    return ret;
}

static int
linked_attrs_add_pre_op(Slapi_PBlock *pb)
{
    return linked_attrs_pre_op(pb, LDAP_CHANGETYPE_ADD);
}

static int
linked_attrs_mod_pre_op(Slapi_PBlock *pb)
{
    return linked_attrs_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
}

static int
linked_attrs_mod_post_op(Slapi_PBlock *pb)
{
    Slapi_Mods *smods = NULL;
    Slapi_Mod *smod = NULL;
    LDAPMod **mods;
    Slapi_Mod *next_mod = NULL;
    char *dn = NULL;
    struct configEntry *config = NULL;
    void *caller_id = NULL;
    int rc = SLAPI_PLUGIN_SUCCESS;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_mod_post_op\n");

    /* We don't want to process internal modify
     * operations that originate from this plugin.
     * Doing so could cause a deadlock. */
    slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &caller_id);

    if (caller_id == linked_attrs_get_plugin_id()) {
        /* Just return without processing */
        return SLAPI_PLUGIN_SUCCESS;
    }

    if (linked_attrs_oktodo(pb) &&
        (dn = linked_attrs_get_dn(pb))) {
        /* First check if the config is being modified. */
        if (linked_attrs_dn_is_config(dn)) {
            linked_attrs_load_config();
        }

        /* get the mod set */
        slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
        smods = slapi_mods_new();
        slapi_mods_init_byref(smods, mods);

        next_mod = slapi_mod_new();
        smod = slapi_mods_get_first_smod(smods, next_mod);
        while (smod) {
            char *type = (char *)slapi_mod_get_type(smod);

            /* See if there is an applicable link configured. */
            linked_attrs_read_lock();

            /* Bail out if the plug-in close function was just called. */
            if (!slapi_plugin_running(pb)) {
                linked_attrs_unlock();
                slapi_mod_free(&next_mod);
                slapi_mods_free(&smods);
                return SLAPI_PLUGIN_SUCCESS;
            }

            linked_attrs_find_config(dn, type, &config);

            /* If we have a matching config entry, we have
             * work to do. If not, we can go to the next smod. */
            if (config) {
                int op = slapi_mod_get_operation(smod);

                /* Prevent other threads from managing
                 * this specific link at the same time. */
                slapi_lock_mutex(config->lock);

                switch (op & ~LDAP_MOD_BVALUES) {
                case LDAP_MOD_ADD:
                    /* Find the entries pointed to by the new
                     * values and add the backpointers. */
                    rc = linked_attrs_add_backpointers(pb, dn, config, smod);
                    break;
                case LDAP_MOD_DELETE:
                    /* Find the entries pointed to by the deleted
                     * values and remove the backpointers. */
                    rc = linked_attrs_del_backpointers(pb, dn, config, smod);
                    break;
                case LDAP_MOD_REPLACE:
                    /* Find the entries pointed to by the deleted
                     * values and remove the backpointers.  If
                     * any new values are being added, find those
                     * entries and add the backpointers. */
                    rc = linked_attrs_replace_backpointers(pb, dn, config, smod);
                    break;
                default:
                    slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                                  "linked_attrs_mod_post_op - Unknown mod type\n");
                    rc = SLAPI_PLUGIN_FAILURE;
                    break;
                }

                slapi_unlock_mutex(config->lock);
                if (rc != LDAP_SUCCESS) {
                    slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                  "linked_attrs_mod_post_op - Update failed (%d)\n", rc);
                    linked_attrs_unlock();
                    slapi_mod_done(next_mod);
                    break;
                }
            }

            config = NULL;
            linked_attrs_unlock();
            slapi_mod_done(next_mod);
            smod = slapi_mods_get_next_smod(smods, next_mod);
        }

        slapi_mod_free(&next_mod);
        slapi_mods_free(&smods);
    }

    if (rc) {
        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
        rc = SLAPI_PLUGIN_FAILURE;
    }
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_mod_post_op (%d)\n", rc);

    return rc;
}

static int
linked_attrs_add_post_op(Slapi_PBlock *pb)
{
    Slapi_Entry *e = NULL;
    char *dn = NULL;
    int rc = SLAPI_PLUGIN_SUCCESS;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_add_post_op\n");

    /* Reload config if a config entry was added. */
    if ((dn = linked_attrs_get_dn(pb))) {
        if (linked_attrs_dn_is_config(dn))
            linked_attrs_load_config();
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_add_post_op - Error "
                      "retrieving dn\n");
    }

    /* Get the newly added entry. */
    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e);

    if (e) {
        Slapi_Attr *attr = NULL;
        char *type = NULL;
        struct configEntry *config = NULL;

        slapi_entry_first_attr(e, &attr);
        while (attr) {
            slapi_attr_get_type(attr, &type);

            /* See if there is an applicable link configured. */
            linked_attrs_read_lock();

            /* Bail out if the plug-in close function was just called. */
            if (!slapi_plugin_running(pb)) {
                linked_attrs_unlock();
                return SLAPI_PLUGIN_SUCCESS;
            }

            linked_attrs_find_config(dn, type, &config);

            /* If config was found, add the backpointers to this entry. */
            if (config) {
                Slapi_ValueSet *vals = NULL;

                slapi_attr_get_valueset(attr, &vals);
                slapi_lock_mutex(config->lock);

                rc = linked_attrs_mod_backpointers(pb, dn, config->managedtype,
                                                   config->scope, LDAP_MOD_ADD, vals);

                slapi_unlock_mutex(config->lock);
                slapi_valueset_free(vals);
                if (rc != LDAP_SUCCESS) {
                    slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                  "linked_attrs_add_post_op - Update failed (%d)\n", rc);
                    linked_attrs_unlock();
                    break;
                }
            }

            config = NULL;
            linked_attrs_unlock();

            slapi_entry_next_attr(e, attr, &attr);
        }
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_add_post_op - Error "
                      "retrieving post-op entry %s\n",
                      dn);
    }

    if (rc) {
        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
        rc = SLAPI_PLUGIN_FAILURE;
    }
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_add_post_op\n");

    return rc;
}

static int
linked_attrs_del_post_op(Slapi_PBlock *pb)
{
    char *dn = NULL;
    Slapi_Entry *e = NULL;
    int rc = SLAPI_PLUGIN_SUCCESS;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_del_post_op\n");

    /* Just bail if we aren't ready to service requests yet. */
    if (!linked_attrs_oktodo(pb)) {
        return rc;
    }

    /* Reload config if a config entry was deleted. */
    if ((dn = linked_attrs_get_dn(pb))) {
        if (linked_attrs_dn_is_config(dn))
            linked_attrs_load_config();
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_del_post_op - Error "
                      "retrieving dn\n");
    }

    /* Get deleted entry, then go through types to find config. */
    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &e);

    if (e) {
        Slapi_Attr *attr = NULL;
        char *type = NULL;
        struct configEntry *config = NULL;

        slapi_entry_first_attr(e, &attr);
        while (attr) {
            slapi_attr_get_type(attr, &type);

            /* See if there is an applicable link configured. */
            linked_attrs_read_lock();

            /* Bail out if the plug-in close function was just called. */
            if (!slapi_plugin_running(pb)) {
                linked_attrs_unlock();
                return rc;
            }

            linked_attrs_find_config(dn, type, &config);

            /* If config was found, delete the backpointers to this entry. */
            if (config) {
                Slapi_ValueSet *vals = NULL;

                slapi_attr_get_valueset(attr, &vals);
                slapi_lock_mutex(config->lock);

                rc = linked_attrs_mod_backpointers(pb, dn, config->managedtype,
                                                   config->scope, LDAP_MOD_DELETE, vals);

                slapi_unlock_mutex(config->lock);
                slapi_valueset_free(vals);
                if (rc != LDAP_SUCCESS) {
                    slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                  "linked_attrs_del_post_op - Update failed (%d)\n", rc);
                    linked_attrs_unlock();
                    break;
                }
            }

            config = NULL;

            /* See if any of the values for this attribute are managed
             * backpointers.  We need to remove the forward link if so. */
            if (linked_attrs_config_index_has_type(type)) {
                int hint = 0;
                Slapi_Value *val = NULL;

                /* Loop through values and see if we have matching config */
                hint = slapi_attr_first_value(attr, &val);
                while (val) {
                    linked_attrs_find_config_reverse(slapi_value_get_string(val),
                                                     type, &config);

                    if (config) {
                        Slapi_ValueSet *vals = slapi_valueset_new();

                        slapi_valueset_add_value(vals, val);
                        slapi_lock_mutex(config->lock);

                        /* Delete forward link value. */
                        rc = linked_attrs_mod_backpointers(pb, dn, config->linktype,
                                                           config->scope, LDAP_MOD_DELETE, vals);

                        slapi_unlock_mutex(config->lock);
                        slapi_valueset_free(vals);
                        config = NULL;
                        if (rc != LDAP_SUCCESS) {
                            slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                          "linked_attrs_del_post_op - Update failed (%d)\n", rc);
                            linked_attrs_unlock();
                            goto bail;
                        }
                    }

                    hint = slapi_attr_next_value(attr, hint, &val);
                }
            }

            linked_attrs_unlock();

            slapi_entry_next_attr(e, attr, &attr);
        }
    } else {
        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_del_post_op - Error "
                      "retrieving pre-op entry %s\n",
                      dn);
        rc = SLAPI_PLUGIN_FAILURE;
    }

bail:
    if (rc) {
        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
        rc = SLAPI_PLUGIN_FAILURE;
    }
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_del_post_op\n");

    return rc;
}

static int
linked_attrs_modrdn_post_op(Slapi_PBlock *pb)
{
    char *old_dn = NULL;
    char *new_dn = NULL;
    Slapi_Entry *post_e = NULL;
    Slapi_Attr *attr = NULL;
    char *type = NULL;
    struct configEntry *config = NULL;
    int rc = SLAPI_PLUGIN_SUCCESS;

    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "--> linked_attrs_modrdn_post_op\n");

    /* Just bail if we aren't ready to service requests yet. */
    if (!linked_attrs_oktodo(pb)) {
        goto done;
    }

    /* Reload config if an existing config entry was renamed,
     * or if the new dn brings an entry into the scope of the
     * config entries. */
    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &post_e);
    if (post_e) {
        new_dn = slapi_entry_get_ndn(post_e);
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_modrdn_post_op - Error "
                      "retrieving post-op entry\n");
        rc = LDAP_OPERATIONS_ERROR;
        goto done;
    }

    if ((old_dn = linked_attrs_get_dn(pb))) {
        if (linked_attrs_dn_is_config(old_dn) || linked_attrs_dn_is_config(new_dn))
            linked_attrs_load_config();
    } else {
        slapi_log_err(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
                      "linked_attrs_modrdn_post_op - Error "
                      "retrieving dn\n");
        rc = LDAP_OPERATIONS_ERROR;
        goto done;
    }

    /* Check if this operation requires any updates to links. */
    slapi_entry_first_attr(post_e, &attr);
    while (attr) {
        slapi_attr_get_type(attr, &type);

        /* See if there is an applicable link configured. */
        linked_attrs_read_lock();

        /* Bail out if the plug-in close function was just called. */
        if (!slapi_plugin_running(pb)) {
            linked_attrs_unlock();
            return SLAPI_PLUGIN_SUCCESS;
        }

        linked_attrs_find_config(old_dn, type, &config);

        /* If config was found for the old dn, delete the backpointers
         * to this entry. */
        if (config) {
            Slapi_ValueSet *vals = NULL;

            slapi_attr_get_valueset(attr, &vals);
            slapi_lock_mutex(config->lock);

            /* Delete old dn value. */
            rc = linked_attrs_mod_backpointers(pb, old_dn, config->managedtype,
                                               config->scope, LDAP_MOD_DELETE, vals);

            slapi_unlock_mutex(config->lock);
            slapi_valueset_free(vals);
            config = NULL;
            if (rc != LDAP_SUCCESS) {
                slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_modrdn_post_op - Update failed(old type) (%d)\n", rc);
                linked_attrs_unlock();
                break;
            }
        }

        linked_attrs_find_config(new_dn, type, &config);

        /* If config was found for the new dn, add the backpointers
         * to this entry.  We do this separate check for both dn's
         * to catch an entry that comes into or goes out of scope
         * from the MODRDN operation. */
        if (config) {
            Slapi_ValueSet *vals = NULL;

            slapi_attr_get_valueset(attr, &vals);
            slapi_lock_mutex(config->lock);

            /* Add new dn value. */
            rc = linked_attrs_mod_backpointers(pb, new_dn, config->managedtype,
                                               config->scope, LDAP_MOD_ADD, vals);

            slapi_unlock_mutex(config->lock);
            slapi_valueset_free(vals);
            config = NULL;
            if (rc != LDAP_SUCCESS) {
                slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                              "linked_attrs_modrdn_post_op - Update failed(new type) (%d)\n", rc);
                linked_attrs_unlock();
                break;
            }
        }

        /* See if any of the values for this attribute are managed
         * backpointers.  We need to update the forward link if so. */
        if (linked_attrs_config_index_has_type(type)) {
            int hint = 0;
            Slapi_Value *val = NULL;

            /* Loop through values and see if we have matching config */
            hint = slapi_attr_first_value(attr, &val);
            while (val) {
                linked_attrs_find_config_reverse(slapi_value_get_string(val),
                                                 type, &config);

                /* If the new DN is within scope, we should fixup the forward links. */
                if (config && slapi_dn_issuffix(new_dn, (config->scope))) {
                    Slapi_ValueSet *vals = slapi_valueset_new();

                    slapi_valueset_add_value(vals, val);
                    slapi_lock_mutex(config->lock);

                    /* Delete old dn value. */
                    rc = linked_attrs_mod_backpointers(pb, old_dn, config->linktype,
                                                       config->scope, LDAP_MOD_DELETE, vals);
                    if (rc != LDAP_SUCCESS) {
                        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                      "linked_attrs_modrdn_post_op - Update failed(old dn) (%d)\n", rc);
                        slapi_unlock_mutex(config->lock);
                        slapi_valueset_free(vals);
                        linked_attrs_unlock();
                        goto done;
                    }

                    /* Add new dn value. */
                    rc = linked_attrs_mod_backpointers(pb, new_dn, config->linktype,
                                                       config->scope, LDAP_MOD_ADD, vals);

                    slapi_unlock_mutex(config->lock);
                    slapi_valueset_free(vals);
                    config = NULL;
                    if (rc != LDAP_SUCCESS) {
                        slapi_log_err(SLAPI_LOG_ERR, LINK_PLUGIN_SUBSYSTEM,
                                      "linked_attrs_modrdn_post_op - Update failed(new dn) (%d)\n", rc);
                        linked_attrs_unlock();
                        goto done;
                    }
                }

                hint = slapi_attr_next_value(attr, hint, &val);
            }
        }

        linked_attrs_unlock();

        slapi_entry_next_attr(post_e, attr, &attr);
    }

done:
    slapi_log_err(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
                  "<-- linked_attrs_modrdn_post_op\n");
    if (rc) {
        slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
        rc = SLAPI_PLUGIN_FAILURE;
    }

    return rc;
}


/*
 * Debug functions to print config
 */
void
linked_attrs_dump_config()
{
    PRCList *list;

    linked_attrs_read_lock();

    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
        list = PR_LIST_HEAD(g_link_config);
        while (list != g_link_config) {
            linked_attrs_dump_config_entry((struct configEntry *)list);
            list = PR_NEXT_LINK(list);
        }
    }

    linked_attrs_unlock();
}

void
linked_attrs_dump_config_index()
{
    PRCList *list;

    linked_attrs_read_lock();

    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
        list = PR_LIST_HEAD(g_managed_config_index);
        while (list != g_managed_config_index) {
            linked_attrs_dump_config_entry(((struct configIndex *)list)->config);
            list = PR_NEXT_LINK(list);
        }
    }

    linked_attrs_unlock();
}


void
linked_attrs_dump_config_entry(struct configEntry *entry)
{
    slapi_log_err(SLAPI_LOG_DEBUG, LINK_PLUGIN_SUBSYSTEM,
                  "<==== Linked Attribute Pair =====>\n");
    slapi_log_err(SLAPI_LOG_DEBUG, LINK_PLUGIN_SUBSYSTEM,
                  "<---- config entry dn -----> %s\n", entry->dn);
    slapi_log_err(SLAPI_LOG_DEBUG, LINK_PLUGIN_SUBSYSTEM,
                  "<---- link type -----------> %s\n", entry->linktype);
    slapi_log_err(SLAPI_LOG_DEBUG, LINK_PLUGIN_SUBSYSTEM,
                  "<---- managed type --------> %s\n", entry->managedtype);
    slapi_log_err(SLAPI_LOG_DEBUG, LINK_PLUGIN_SUBSYSTEM,
                  "<---- scope ---------------> %s\n", entry->scope);
}
