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

/*
 * Object Management Functions
 * (as defined in PKCS#11 spec section 11.7)
 */

#include <strings.h>
#include "metaGlobal.h"
#include <stdio.h>

#define FIND_OBJ_BUF_SIZE       512     /* size of buf used for C_FindObjects */

/*
 * Argument related return codes. Will return to the caller immediately,
 * and not try the operation on another slot.
 */
static CK_RV stop_rv[] = {
        CKR_ARGUMENTS_BAD,
        CKR_ATTRIBUTE_TYPE_INVALID,
        CKR_DOMAIN_PARAMS_INVALID,
        CKR_TEMPLATE_INCOMPLETE
};
static int num_stop_rv = sizeof (stop_rv) / sizeof (CK_RV);

/*
 * Return codes that are related to a specific slot.
 * Will try to perform the operation in the next available slot.
 * If all attempts failed, will return the error code from the first slot.
 *
 * This list is here for reference only, it is commented out because
 * it doesn't need to be used by the code at this point.
 *
 * static CK_RV try_again_rv[] = {
 *      CKR_DEVICE_ERROR,
 *      CKR_DEVICE_MEMORY,
 *      CKR_DEVICE_REMOVED,
 *      CKR_FUNCTION_FAILED,
 *      CKR_GENERAL_ERROR,
 *      CKR_HOST_MEMORY,
 *      CKR_TEMPLATE_INCONSISTENT,
 *      CKR_ATTRIBUTE_READ_ONLY,
 *      CKR_ATTRIBUTE_VALUE_INVALID
 * };
 * static int num_try_again_rv = sizeof (try_again_rv) / sizeof (CK_RV);
 */

/*
 * We should never get these return codes because
 * MetaSlot is the one that actually created the
 * sessions.  When we get these errors in C_CreateObject,
 * will try to create the object in the next available slot.
 * If all attempts failed, will return CKR_FUNCTION_FAILED
 * to the caller.
 */
static CK_RV other_rv[] = {
        CKR_CRYPTOKI_NOT_INITIALIZED,
        CKR_SESSION_CLOSED,
        CKR_SESSION_HANDLE_INVALID,
        CKR_SESSION_READ_ONLY
};
static int num_other_rv = sizeof (other_rv) / sizeof (CK_RV);

/*
 * This function is only used by the C_CreateObject and C_CopyObject.
 *
 * It is used to determine if the operation should be tried on another slot
 * based on the return code
 */
static boolean_t
try_again(CK_RV rv)
{
        int i;

        for (i = 0; i < num_stop_rv; i++) {
                if (rv == stop_rv[i]) {
                        return (B_FALSE);
                }
        }
        return (B_TRUE);
}


/*
 * meta_CreateObject
 *
 */
CK_RV
meta_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject)
{
        CK_RV rv;
        meta_session_t *session;
        slot_session_t *slot_session = NULL;
        meta_object_t *object = NULL;
        slot_object_t *slot_object = NULL;
        CK_OBJECT_HANDLE hNewObject;
        CK_ULONG slot_num, keystore_slotnum;
        CK_RV first_rv;

        if (pTemplate == NULL || ulCount < 1 || phObject == NULL)
                return (CKR_ARGUMENTS_BAD);

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        rv = meta_object_alloc(session, &object);
        if (rv != CKR_OK)
                goto cleanup;

        /*
         * Create a clone of the object
         */
        rv = meta_slot_object_alloc(&slot_object);
        if (rv != CKR_OK)
                goto cleanup;

        /*
         * Set to true (token object) if template has CKA_TOKEN=true;
         * otherwise, it is false (session object).
         */
        (void) get_template_boolean(CKA_TOKEN, pTemplate, ulCount,
            &(object->isToken));

        /* Can't create token objects in a read-only session. */
        if ((IS_READ_ONLY_SESSION(session->session_flags)) && object->isToken) {
                rv = CKR_SESSION_READ_ONLY;
                goto cleanup;
        }

        /*
         * Set to true (private object) if template has CKA_PRIVATE=true;
         * otherwise, it is false (public object).
         */
        (void) get_template_boolean(CKA_PRIVATE, pTemplate, ulCount,
            &(object->isPrivate));

        /* Assume object is extractable unless template has otherwise */
        object->isExtractable = B_TRUE;
        (void) get_template_boolean(CKA_EXTRACTABLE, pTemplate, ulCount,
            &(object->isExtractable));

        /*
         * Set to true (sensitive object) if template has CKA_SENSITIVE=true;
         * otherwise, it is false.
         */
        (void) get_template_boolean(CKA_SENSITIVE, pTemplate, ulCount,
            &(object->isSensitive));

        /*
         * Check if this can be a FreeObject.
         *
         * For creating objects, this check is mostly for preventing
         * non-keystore hardware from creating CKA_PRIVATE objects without
         * logging in.
         */

        if (meta_freeobject_check(session, object, NULL, pTemplate, ulCount,
            0)) {
                /*
                 * Make sure we are logged into the keystore if this is a
                 * private freetoken object.
                 */
                if (object->isPrivate && !metaslot_logged_in())
                        return (CKR_USER_NOT_LOGGED_IN);

                if (!meta_freeobject_set(object, pTemplate, ulCount, B_TRUE))
                        goto cleanup;
        }


        keystore_slotnum = get_keystore_slotnum();

        if (object->isToken || object->isFreeToken == FREE_ENABLED) {

                /*
                 * If this is a token object or a FreeToken then create it
                 * on the keystore slot.
                 */

                slot_num = keystore_slotnum;
                rv = meta_get_slot_session(slot_num, &slot_session,
                    session->session_flags);
                if (rv != CKR_OK)
                        goto cleanup;

                object->tried_create_clone[slot_num] = B_TRUE;
                rv = FUNCLIST(slot_session->fw_st_id)->C_CreateObject(
                    slot_session->hSession, pTemplate, ulCount, &hNewObject);

                if (rv != CKR_OK)
                        goto cleanup;

        } else {

                /*
                 * Create a clone of the object in the first available slot.
                 *
                 * If creating a clone in a specific slot failed, it will
                 * either stop and return the error to the user, or try
                 * again in the next available slot until it succeeds.  The
                 * decision to stop or continue is made based on the return
                 * code.
                 */
                CK_ULONG num_slots = meta_slotManager_get_slotcount();

                for (slot_num = 0; slot_num < num_slots; slot_num++) {
                        /*
                         * If this is a free token and we are on the keystore
                         * slot, bypass this because it was already created
                         */

                        rv = meta_get_slot_session(slot_num, &slot_session,
                            session->session_flags);
                        if (rv != CKR_OK)
                                goto cleanup;

                        object->tried_create_clone[slot_num] = B_TRUE;
                        rv = FUNCLIST(slot_session->fw_st_id)->C_CreateObject(
                            slot_session->hSession, pTemplate, ulCount,
                            &hNewObject);
                        if (rv == CKR_OK)
                                break;

                        if (!try_again(rv))
                                goto cleanup;

                        /* save first rv for other errors */
                        if (slot_num == 0)
                                first_rv = rv;

                        meta_release_slot_session(slot_session);
                        slot_session = NULL;

                }
        }

        if (rv == CKR_OK) {
                slot_object->hObject = hNewObject;
                object->clones[slot_num] = slot_object;
                object->master_clone_slotnum = slot_num;

                /* Allow FreeToken to activate onto token obj list */
                if (object->isFreeToken == FREE_ENABLED)
                        object->isToken = B_TRUE;

                meta_slot_object_activate(slot_object, slot_session,
                    object->isToken);

                slot_object = NULL;
                meta_release_slot_session(slot_session);
                slot_session = NULL;

        } else {
                /*
                 * return either first error code or
                 * CKR_FUNCTION_FAILED depending on the failure
                 */
                int i;
                for (i = 0; i < num_other_rv; i++) {
                        if (rv == other_rv[i]) {
                                rv = CKR_FUNCTION_FAILED;
                                goto cleanup;
                        }
                }
                /* need to return first rv */
                rv = first_rv;
                goto cleanup;
        }


        /*
         * always keep a copy of the template for C_CreateObject,
         * so clones can be created on other slots if necessary.
         * This is done even when the CKA_EXTRACTABLE=FALSE flag
         * is set for the object.  The supplied template is
         * "owned" by metaslot.  The application should not be
         * penalized just because metaslot choose to try creating
         * the object in a slot that's not capable of performing
         * any future operation.
         */
        rv = get_master_attributes_by_template(pTemplate, ulCount,
            &object->attributes, &object->num_attributes);
        if (rv == CKR_OK) {
                CK_ULONG i;
                for (i = 0; i < ulCount; i++) {
                        rv = attribute_set_value(&(pTemplate[i]),
                            object->attributes, object->num_attributes);
                }
        }

        meta_object_activate(object);
        *phObject = (CK_OBJECT_HANDLE) object;

        REFRELEASE(session);

        return (CKR_OK);

cleanup:
        if (slot_object)
                meta_slot_object_dealloc(slot_object);
        if (slot_session)
                meta_release_slot_session(slot_session);
        if (object)
                (void) meta_object_dealloc(session, object, B_TRUE);

        REFRELEASE(session);

        return (rv);
}


/*
 * meta_CopyObject
 *
 */
CK_RV
meta_CopyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
    CK_OBJECT_HANDLE_PTR phNewObject)
{
        CK_RV rv, first_rv;
        meta_session_t *session;
        meta_object_t *src_object, *dst_object = NULL;
        slot_session_t *slot_session = NULL;
        slot_object_t *dst_slot_object = NULL;
        CK_ULONG i;
        slot_object_t *src_slot_object;
        CK_ULONG slotnum, num_slots;
        boolean_t found;

        if (pTemplate == NULL && ulCount != 0)
                return (CKR_ARGUMENTS_BAD);
        if (phNewObject == NULL)
                return (CKR_ARGUMENTS_BAD);

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        rv = meta_handle2object(hObject, &src_object);
        if (rv != CKR_OK) {
                REFRELEASE(session);
                return (rv);
        }

        rv = meta_object_alloc(session, &dst_object);
        if (rv != CKR_OK)
                goto finish;

        found = get_template_boolean(CKA_TOKEN,
            pTemplate, ulCount, &(dst_object->isToken));
        if (!found) {
                dst_object->isToken = src_object->isToken;
                if (src_object->isFreeToken == FREE_ENABLED)
                        dst_object->isToken = TRUE;
                else
                        dst_object->isToken = src_object->isToken;
        }

        /* Can't create token objects in a read-only session. */
        if ((IS_READ_ONLY_SESSION(session->session_flags)) &&
            (dst_object->isToken)) {
                rv = CKR_SESSION_READ_ONLY;
                goto finish;
        }

        if (dst_object->isToken) {

                /*
                 * if the dst object is a token object, and the source
                 * object is not, the source object needs to be extractable.
                 * Otherwise, the source object needs to reside in the
                 * token object slot
                 */
                if ((!src_object->isExtractable) &&
                    (src_object->master_clone_slotnum
                    != get_keystore_slotnum())) {
                        rv = CKR_FUNCTION_FAILED;
                        goto finish;
                }

                /* determine if dst is going to be private object or not */
                found = get_template_boolean(CKA_PRIVATE,
                    pTemplate, ulCount, &(dst_object->isPrivate));
                if (!found) {
                        /* will be the same as the source object */
                        dst_object->isPrivate = src_object->isPrivate;
                }

                slotnum = get_keystore_slotnum();
        } else {

                /* try create the obj in the same slot as the source obj */
                slotnum = src_object->master_clone_slotnum;
        }

        rv = meta_slot_object_alloc(&dst_slot_object);
        if (rv != CKR_OK)
                goto finish;

        rv = meta_get_slot_session(slotnum, &slot_session,
            session->session_flags);
        if (rv != CKR_OK)
                goto finish;

        rv = meta_object_get_clone(src_object, slotnum,
            slot_session, &src_slot_object);
        if (rv != CKR_OK)
                goto finish;

        dst_object->tried_create_clone[slotnum] = B_TRUE;
        rv = FUNCLIST(slot_session->fw_st_id)->C_CopyObject(
            slot_session->hSession, src_slot_object->hObject, pTemplate,
            ulCount, &(dst_slot_object->hObject));

        if (rv != CKR_OK) {
                if (dst_object->isToken) {
                        /*
                         * token obj can only be created in the
                         * token slot.  No need to try anywhere else
                         */
                        goto finish;
                }
                if ((!src_object->isExtractable) ||
                    ((src_object->isSensitive) && (src_object->isToken) &&
                    (!metaslot_auto_key_migrate))) {
                        /* source object isn't clonable in another slot */
                        goto finish;
                }

                if (!try_again(rv)) {
                        goto finish;
                }

                first_rv = rv;

                meta_release_slot_session(slot_session);
                slot_session = NULL;

                num_slots = meta_slotManager_get_slotcount();

                /* Try operation on other slots if the object is clonable */
                for (slotnum = 0; slotnum < num_slots; slotnum++) {

                        if (slotnum == src_object->master_clone_slotnum) {
                                /* already tried, don't need to try again */
                                continue;
                        }

                        rv = meta_get_slot_session(slotnum, &slot_session,
                            session->session_flags);
                        if (rv != CKR_OK) {
                                goto finish;
                        }

                        rv = meta_object_get_clone(src_object, slotnum,
                            slot_session, &src_slot_object);
                        if (rv != CKR_OK)
                                goto finish;

                        dst_object->tried_create_clone[slotnum] = B_TRUE;

                        rv = FUNCLIST(slot_session->fw_st_id)->C_CopyObject(
                            slot_session->hSession, src_slot_object->hObject,
                            pTemplate, ulCount, &dst_slot_object->hObject);

                        if (rv == CKR_OK) {
                                break;
                        }

                        if (!try_again(rv)) {
                                goto finish;
                        }
                        meta_release_slot_session(slot_session);
                        slot_session = NULL;
                }
        }

        if (rv == CKR_OK) {

                rv = meta_object_get_attr(slot_session,
                    dst_slot_object->hObject, dst_object);
                if (rv != CKR_OK) {
                        goto finish;
                }

                if (src_object->attributes != NULL) {

                        /* Keep a copy of the template for the future */

                        /*
                         * Don't allow attributes to change while
                         * we look at them.
                         */
                        (void) pthread_rwlock_rdlock(
                            &src_object->attribute_lock);

                        rv = get_master_attributes_by_duplication(
                            src_object->attributes,
                            src_object->num_attributes,
                            &dst_object->attributes,
                            &dst_object->num_attributes);

                        (void) pthread_rwlock_unlock(
                            &src_object->attribute_lock);

                        if (rv != CKR_OK)
                                goto finish;

                        for (i = 0; i < ulCount; i++) {
                                rv = attribute_set_value(pTemplate + i,
                                    dst_object->attributes,
                                    dst_object->num_attributes);

                                if (rv != CKR_OK)
                                        goto finish;
                        }
                }

                /* Allow FreeToken to activate onto token obj list */
                if (dst_object->isFreeToken == FREE_ENABLED)
                        dst_object->isToken = TRUE;

                meta_slot_object_activate(dst_slot_object,
                    slot_session, dst_object->isToken);

                dst_object->clones[slotnum] = dst_slot_object;
                dst_object->master_clone_slotnum = slotnum;
                dst_slot_object = NULL; /* for error cleanup */

                meta_release_slot_session(slot_session);
                slot_session = NULL; /* for error cleanup */

        } else {
                /*
                 * return either first error code or
                 * CKR_FUNCTION_FAILED depending on the failure
                 */
                int j;
                for (j = 0; j < num_other_rv; j++) {
                        if (rv == other_rv[j]) {
                                rv = CKR_FUNCTION_FAILED;
                                goto finish;
                        }
                }
                /* need to return first rv */
                rv = first_rv;
                goto finish;
        }
        meta_object_activate(dst_object);
        *phNewObject = (CK_OBJECT_HANDLE) dst_object;

finish:
        if (rv != CKR_OK) {
                if (dst_slot_object)
                        meta_slot_object_dealloc(dst_slot_object);

                if (dst_object)
                        (void) meta_object_dealloc(session, dst_object,
                            B_TRUE);

                if (slot_session)
                        meta_release_slot_session(slot_session);
        }

        OBJRELEASE(src_object);
        REFRELEASE(session);

        return (rv);
}


/*
 * meta_DestroyObject
 *
 * This function destroys an object by first removing it from the
 * list of valid objects for a given session (if session object) or
 * the global token object list.  And then, calling C_DestroyObject
 * on all the slots on which we have created a clone of this object.
 */
CK_RV
meta_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
{
        CK_RV rv;
        meta_session_t *session;
        meta_object_t *object;

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        rv = meta_handle2object(hObject, &object);
        if (rv != CKR_OK) {
                REFRELEASE(session);
                return (rv);
        }

        /* Can't delete token objects from a read-only session. */
        if ((IS_READ_ONLY_SESSION(session->session_flags)) &&
            (object->isToken || object->isFreeToken == FREE_ENABLED)) {
                OBJRELEASE(object);
                REFRELEASE(session);
                return (CKR_SESSION_READ_ONLY);
        }

        /* Remove object from list of valid meta_objects */
        rv = meta_object_deactivate(object, B_FALSE, B_TRUE);

        /*
         * Actually call C_DestroyObject on all the slots on which we have
         * created a clone of this object.
         */
        if (rv == CKR_OK)
                rv = meta_object_dealloc(session, object, B_TRUE);

        REFRELEASE(session);

        return (rv);
}


/*
 * meta_GetObjectSize
 *
 * NOTES:
 * 1) Because the "size" is so poorly defined in the spec, we have deemed
 *    it useless and won't support it. This is especially true for the
 *    metaslot, because the mulitple providers it uses may each interpret
 *    the size differently.
 */
/* ARGSUSED */
CK_RV
meta_GetObjectSize(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ULONG_PTR pulSize)
{
        return (CKR_FUNCTION_NOT_SUPPORTED);
}


/*
 * meta_GetAttributeValue
 *
 */
CK_RV
meta_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
        CK_RV rv;
        meta_session_t *session;
        meta_object_t *object;
        CK_ULONG slotnum;
        slot_session_t *slot_session;

        if (pTemplate == NULL || ulCount < 1)
                return (CKR_ARGUMENTS_BAD);

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        rv = meta_handle2object(hObject, &object);
        if (rv != CKR_OK) {
                REFRELEASE(session);
                return (rv);
        }

        slotnum = object->master_clone_slotnum;

        rv = meta_get_slot_session(slotnum, &slot_session,
            session->session_flags);
        if (rv == CKR_OK) {
                rv = FUNCLIST(slot_session->fw_st_id)->C_GetAttributeValue(
                    slot_session->hSession, object->clones[slotnum]->hObject,
                    pTemplate, ulCount);

                meta_release_slot_session(slot_session);
        }

        OBJRELEASE(object);
        REFRELEASE(session);

        return (rv);

}


/*
 * meta_SetAttributeValue
 *
 * Call C_SetAttributeValue on all the clones.  If the operation fails on
 * all clones, return the failure.
 *
 * If the operation fails on some clones and not the others, delete all the
 * clones that have failed the operation.  If any of the deleted clone is the
 * master clone, use one of the remaining clone as the master clone.
 *
 * If the operation is successful and the master template already exists,
 * update the master template with new values.
 */
CK_RV
meta_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
        CK_RV rv = CKR_OK, save_rv = CKR_OK;
        meta_session_t *session;
        meta_object_t *object;
        CK_ULONG slotnum, num_slots;
        /* Keep track of which slot's SetAttributeValue failed */
        boolean_t *clone_failed_op = NULL;
        int num_clones = 0, num_clones_failed = 0;
        slot_session_t *slot_session;
        slot_object_t *slot_object;
        boolean_t need_update_master_clone = B_FALSE;

        if (pTemplate == NULL || ulCount < 1)
                return (CKR_ARGUMENTS_BAD);

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        rv = meta_handle2object(hObject, &object);
        if (rv != CKR_OK) {
                REFRELEASE(session);
                return (rv);
        }

        if ((IS_READ_ONLY_SESSION(session->session_flags)) &&
            (object->isToken || object->isFreeToken == FREE_ENABLED)) {
                rv = CKR_SESSION_READ_ONLY;
                goto finish;
        }

        if ((!object->isExtractable) && (object->attributes == NULL)) {
                /*
                 * object has no clone, just need to do the operation
                 * in the master clone slot
                 */
                slot_session_t *slot_session;
                slotnum = object->master_clone_slotnum;

                rv = meta_get_slot_session(slotnum, &slot_session,
                    session->session_flags);
                if (rv == CKR_OK) {
                        rv = FUNCLIST(slot_session->fw_st_id)->\
                            C_SetAttributeValue(slot_session->hSession,
                            object->clones[slotnum]->hObject, pTemplate,
                            ulCount);

                        meta_release_slot_session(slot_session);
                }
                goto finish;
        }


        num_slots = meta_slotManager_get_slotcount();

        /*
         * object might have clones, need to do operation in all clones
         *
         * If the C_SetAttributeValue() call fails in a clone, the
         * clone that failed the operation can not be deleted right
         * away.  The clone with the failed operation is recorded, and
         * the deletion will happen in a separate loop.
         *
         * This is necessary because if ALL the clones failed
         * C_SetAttributeVAlue(), then, the app's call to C_SetAttributeValue()
         * is considered failed, and there shouldn't be any changes to the
         * object, none of the clones should be deleted.
         * On the other hand, if C_SetAttributeValue() fails in some clones
         * and succeeds in other clones, the C_SetAttributeValue() operation
         * is considered successful, and those clones that failed the
         * operation is deleted.
         */
        clone_failed_op = calloc(num_slots, sizeof (boolean_t));
        if (clone_failed_op == NULL) {
                rv = CKR_HOST_MEMORY;
                goto finish;
        }
        for (slotnum = 0; slotnum < num_slots; slotnum++) {
                if (object->clones[slotnum] != NULL) {
                        num_clones++;
                        rv = meta_get_slot_session(slotnum, &slot_session,
                            session->session_flags);
                        if (rv != CKR_OK) {
                                goto finish;
                        }

                        rv = FUNCLIST(slot_session->fw_st_id)->\
                            C_SetAttributeValue(slot_session->hSession,
                            object->clones[slotnum]->hObject, pTemplate,
                            ulCount);

                        if (rv != CKR_OK) {
                                num_clones_failed++;
                                clone_failed_op[slotnum] = B_TRUE;
                                if (save_rv == CKR_OK) {
                                        save_rv = rv;
                                }
                        }
                        meta_release_slot_session(slot_session);
                }
        }

        if (num_clones_failed == num_clones) {
                /* all operations failed */
                rv = save_rv;
                goto finish;
        }

        if (num_clones_failed > 0) {
                /*
                 * C_SetAttributeValue in some of the clones failed.
                 * Find out which ones failed, and delete the clones
                 * in those failed slots
                 */
                for (slotnum = 0; slotnum < num_slots; slotnum++) {
                        if (clone_failed_op[slotnum]) {

                                slot_object_t *clone = object->clones[slotnum];

                                rv = meta_get_slot_session(slotnum,
                                    &slot_session, session->session_flags);
                                if (rv == CKR_OK) {
                                        (void) FUNCLIST(
                                            slot_session->fw_st_id)->
                                            C_DestroyObject(
                                            slot_session->hSession,
                                            clone->hObject);

                                        meta_release_slot_session(slot_session);

                                }

                                meta_slot_object_deactivate(clone);
                                meta_slot_object_dealloc(clone);
                                object->clones[slotnum] = NULL;

                                if (slotnum == object->master_clone_slotnum) {
                                        need_update_master_clone = B_TRUE;
                                }
                        }
                }

                if (need_update_master_clone) {
                        /* make first available clone the master */
                        for (slotnum = 0; slotnum < num_slots; slotnum++) {
                                if (object->clones[slotnum]) {
                                        object->master_clone_slotnum = slotnum;
                                        need_update_master_clone = B_FALSE;
                                        break;
                                }
                        }

                }
                if (need_update_master_clone) {
                        /*
                         * something is very wrong, can't continue
                         * it should never be this case.
                         */
                        rv = CKR_FUNCTION_FAILED;
                        goto finish;
                }
                rv = CKR_OK;
        }

        /*
         * Update the attribute information we keep in our metaslot object
         */
        slot_object = object->clones[object->master_clone_slotnum];
        rv = meta_get_slot_session(object->master_clone_slotnum,
            &slot_session, session->session_flags);
        if (rv == CKR_OK) {
                (void) meta_object_get_attr(slot_session,
                    slot_object->hObject, object);
                meta_release_slot_session(slot_session);
        }

        /* if there's a copy of the attributes, keep it up to date */
        if (object->attributes != NULL) {

                CK_ULONG i;

                /* Make sure no one else is looking at attributes. */
                (void) pthread_rwlock_wrlock(&object->attribute_lock);

                for (i = 0; i < ulCount; i++) {
                        (void) attribute_set_value(pTemplate + i,
                            object->attributes, object->num_attributes);

                }
                (void) pthread_rwlock_unlock(&object->attribute_lock);
        }

finish:
        if (clone_failed_op) {
                free(clone_failed_op);
        }
        OBJRELEASE(object);
        REFRELEASE(session);

        return (rv);
}

static boolean_t
meta_object_in_list(meta_object_t *obj, meta_object_t **objs_list, int num_objs)
{
        int i;

        for (i = 0; i < num_objs; i++) {
                if (objs_list[i] == obj) {
                        return (B_TRUE);
                }
        }
        return (B_FALSE);
}

static CK_RV
add_to_search_result(meta_object_t *object, find_objs_info_t *info,
    int *num_results_alloc)
{
        /*
         * allocate space for storing results if the currently
         * allocated space is not enough
         */
        if (*num_results_alloc <= info->num_matched_objs) {
                *num_results_alloc += FIND_OBJ_BUF_SIZE;
                info->matched_objs = realloc(info->matched_objs,
                    sizeof (meta_object_t *) * (*num_results_alloc));
                if (info->matched_objs == NULL) {
                        return (CKR_HOST_MEMORY);
                }
        }
        (info->matched_objs)[(info->num_matched_objs)++] = object;
        return (CKR_OK);
}

static CK_RV
process_find_results(CK_OBJECT_HANDLE *results, CK_ULONG num_results,
    int *num_results_allocated, find_objs_info_t *info, CK_ULONG slotnum,
    boolean_t token_only, slot_session_t *slot_session,
    meta_session_t *session)
{
        CK_ULONG i;
        meta_object_t *object;
        CK_RV rv;

        for (i = 0; i < num_results; i++) {

                object = meta_object_find_by_handle(results[i], slotnum,
                    token_only);

                /*
                 * a token object is found from the keystore,
                 * need to create a meta object for it
                 */
                if (object == NULL) {
                        slot_object_t *slot_object;

                        rv = meta_object_alloc(session, &object);
                        if (rv != CKR_OK) {
                                return (rv);
                        }

                        rv = meta_slot_object_alloc(&slot_object);
                        if (rv != CKR_OK) {
                                (void) meta_object_dealloc(session, object,
                                    B_TRUE);
                                return (rv);
                        }

                        slot_object->hObject = results[i];
                        object->master_clone_slotnum = slotnum;
                        object->clones[slotnum] = slot_object;

                        /* get in the attributes we keep in meta_object */

                        rv = meta_object_get_attr(slot_session,
                            slot_object->hObject, object);
                        if (rv != CKR_OK) {
                                (void) meta_object_dealloc(session, object,
                                    B_TRUE);
                                return (rv);
                        }

                        meta_slot_object_activate(slot_object, slot_session,
                            B_TRUE);
                        meta_object_activate(object);
                        slot_object = NULL;
                }

                if (!meta_object_in_list(object, info->matched_objs,
                    info->num_matched_objs)) {
                        rv = add_to_search_result(object, info,
                            num_results_allocated);
                        if (rv != CKR_OK) {
                                return (rv);
                        }
                }
        }
        return (CKR_OK);
}

static CK_RV
meta_search_for_objects(meta_session_t *session, find_objs_info_t *info,
    slot_session_t *slot_session, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount, CK_ULONG slotnum, boolean_t token_only,
    int *num_results_alloc)
{
        CK_ULONG tmp_num_results;
        CK_OBJECT_HANDLE tmp_results[FIND_OBJ_BUF_SIZE];
        CK_SESSION_HANDLE hSession = slot_session->hSession;
        CK_RV rv;
        CK_SLOT_ID fw_st_id = slot_session->fw_st_id;

        rv = FUNCLIST(fw_st_id)->C_FindObjectsInit(hSession,
            pTemplate, ulCount);

        if (rv != CKR_OK) {
                return (rv);
        }

        tmp_num_results = 0;
        rv = FUNCLIST(fw_st_id)->C_FindObjects(hSession, tmp_results,
            FIND_OBJ_BUF_SIZE, &tmp_num_results);
        if (rv != CKR_OK) {
                return (rv);
        }

        rv = process_find_results(tmp_results, tmp_num_results,
            num_results_alloc, info, slotnum, token_only,
            slot_session, session);
        if (rv != CKR_OK) {
                return (rv);
        }

        while (tmp_num_results == FIND_OBJ_BUF_SIZE) {
                /* might be more results, need to call C_FindObjects again */
                rv = FUNCLIST(fw_st_id)->C_FindObjects(hSession, tmp_results,
                    FIND_OBJ_BUF_SIZE, &tmp_num_results);
                if (rv != CKR_OK) {
                        return (rv);
                }

                rv = process_find_results(tmp_results, tmp_num_results,
                    num_results_alloc, info, slotnum, token_only,
                    slot_session, session);
                if (rv != CKR_OK) {
                        return (rv);
                }
        }

        rv = FUNCLIST(fw_st_id)->C_FindObjectsFinal(hSession);
        return (rv);
}


/*
 * meta_FindObjectsInit
 *
 * This function actually will do ALL the work of searching for objects
 * that match all requirements specified in the template.
 *
 * Objects that matched the template will be stored in the
 * session's data structure.  When the subsequent C_FindObjects()
 * calls are made, results saved will be returned.
 *
 */
CK_RV
meta_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount)
{
        CK_RV rv;
        meta_session_t *session;
        CK_ULONG slot_num = 0;
        boolean_t have_token_attr, tokenTrue = B_FALSE;
        slot_session_t *slot_find_session = NULL;
        int num_results_allocated = 0;
        CK_ULONG keystore_slotnum;

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        if ((session->find_objs_info).op_active) {
                REFRELEASE(session);
                return (CKR_OPERATION_ACTIVE);
        }

        (session->find_objs_info).op_active = B_TRUE;

        REFRELEASE(session);

        /* see if the template indicates token object only or not */
        have_token_attr = get_template_boolean(CKA_TOKEN, pTemplate, ulCount,
            &tokenTrue);

        keystore_slotnum = get_keystore_slotnum();

        if (have_token_attr && tokenTrue) {


                /*
                 * only interested in token objects, just need to search
                 * token object slot
                 */
                rv = meta_get_slot_session(keystore_slotnum,
                    &slot_find_session, session->session_flags);
                if (rv != CKR_OK)  {
                        goto finish;
                }
                rv = meta_search_for_objects(session,
                    &(session->find_objs_info), slot_find_session, pTemplate,
                    ulCount, keystore_slotnum, B_TRUE, &num_results_allocated);
                if (rv != CKR_OK) {
                        goto finish;
                }
        } else {
                CK_ULONG num_slots = meta_slotManager_get_slotcount();
                for (slot_num = 0; slot_num < num_slots; slot_num++) {
                        rv = meta_get_slot_session(slot_num,
                            &slot_find_session, session->session_flags);
                        if (rv != CKR_OK) {
                                goto finish;
                        }

                        /*
                         * if the slot is NOT the token object slot, and
                         * CKA_TOKEN is not specified, need to specified
                         * it to be false explicitly.  This will prevent
                         * us from using token objects that doesn't
                         * belong to the token slot in the case that
                         * more than one slot supports token objects.
                         */

                        if ((slot_num != keystore_slotnum) &&
                            (!have_token_attr)) {
                                CK_BBOOL false = FALSE;
                                CK_ATTRIBUTE_PTR newTemplate;

                                newTemplate = malloc((ulCount + 1) *
                                    sizeof (CK_ATTRIBUTE));
                                if (newTemplate == NULL) {
                                        rv = CKR_HOST_MEMORY;
                                        goto finish;
                                }
                                (void) memcpy(newTemplate + 1, pTemplate,
                                    ulCount * sizeof (CK_ATTRIBUTE));
                                newTemplate[0].type = CKA_TOKEN;
                                newTemplate[0].pValue = &false;
                                newTemplate[0].ulValueLen = sizeof (false);

                                rv = meta_search_for_objects(session,
                                    &(session->find_objs_info),
                                    slot_find_session, newTemplate,
                                    ulCount+1, slot_num, B_FALSE,
                                    &num_results_allocated);
                                free(newTemplate);
                        } else {
                                rv = meta_search_for_objects(session,
                                    &(session->find_objs_info),
                                    slot_find_session, pTemplate, ulCount,
                                    slot_num, B_FALSE,
                                    &num_results_allocated);
                        }

                        if (rv != CKR_OK) {
                                goto finish;
                        }
                        meta_release_slot_session(slot_find_session);
                        slot_find_session = NULL;
                }
        }

finish:
        if (slot_find_session != NULL) {
                meta_release_slot_session(slot_find_session);
        }
        if (rv != CKR_OK) {
                (void) pthread_rwlock_wrlock(&session->session_lock);
                if (((session->find_objs_info).matched_objs) != NULL) {
                        free((session->find_objs_info).matched_objs);
                }
                bzero(&(session->find_objs_info), sizeof (find_objs_info_t));
                (void) pthread_rwlock_unlock(&(session->session_lock));
        }

        return (rv);
}

/*
 * meta_FindObjects
 *
 * This function actually doesn't do any real work in search for the
 * matching object.  All the work is done in FindObjectsInit().  This
 * function will only return the matching objects store in the session's
 * "find_objs_info" variable.
 *
 */
CK_RV
meta_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject,
    CK_ULONG ulMaxObjectCount, CK_ULONG_PTR pulObjectCount)
{
        CK_RV rv;
        find_objs_info_t *info;
        CK_ULONG num_objs_found = 0;
        meta_object_t *obj;
        meta_session_t *session;
        int i;

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        info = &(session->find_objs_info);

        if (!(info->op_active)) {
                REFRELEASE(session);
                return (CKR_OPERATION_NOT_INITIALIZED);
        }

        for (i = info->next_result_index;
            ((num_objs_found < ulMaxObjectCount) &&
            (i < info->num_matched_objs));
            i++) {
                obj = info->matched_objs[i];
                if (obj != NULL) {
                        /* sanity check to see if object is still valid */
                        (void) pthread_rwlock_rdlock(&obj->object_lock);
                        if (obj->magic_marker == METASLOT_OBJECT_MAGIC) {
                                phObject[num_objs_found++] =
                                    (CK_OBJECT_HANDLE)obj;
                        }
                        (void) pthread_rwlock_unlock(&obj->object_lock);
                }
        }
        info->next_result_index = i;
        *pulObjectCount = num_objs_found;
        REFRELEASE(session);
        return (rv);
}


/*
 * meta_FindObjectsFinal
 *
 */
CK_RV
meta_FindObjectsFinal(CK_SESSION_HANDLE hSession)
{
        CK_RV rv;
        find_objs_info_t *info;
        meta_session_t *session;

        rv = meta_handle2session(hSession, &session);
        if (rv != CKR_OK)
                return (rv);

        info = &(session->find_objs_info);

        if (!info->op_active) {
                REFRELEASE(session);
                return (CKR_OPERATION_NOT_INITIALIZED);
        }

        if (info->matched_objs) {
                free(info->matched_objs);
        }

        bzero(info, sizeof (find_objs_info_t));
        REFRELEASE(session);
        return (rv);
}