/*
* 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 (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <stdlib.h>
#include <string.h>
#include "metaGlobal.h"
/*
* The list and the list lock are global for two external uses:
* 1) C_CloseAllSessions need to close the head (repeatedly,
* until no more sessions exist).
* 2) meta_object_find_by_handle needs to walk all sessions,
* searching each session object list for matching objects.
*/
pthread_rwlock_t meta_sessionlist_lock;
meta_session_t *meta_sessionlist_head;
/*
* The following 2 variables are used for tracking the number of
* sessions and number of read-write rw sessions that are currently open.
*
* They are being manipulated in the metaSession.c file, and being
* referenced in the metaSlotToken.c file.
*/
CK_ULONG num_meta_sessions;
CK_ULONG num_rw_meta_sessions;
static pthread_rwlock_t meta_sessionclose_lock;
/*
* meta_sessionManager_initialize
*
* Called from meta_Initialize. Initializes all the variables used
* by the session manager.
*/
CK_RV
meta_sessionManager_initialize()
{
if (pthread_rwlock_init(&meta_sessionlist_lock, NULL) != 0) {
return (CKR_FUNCTION_FAILED);
}
if (pthread_rwlock_init(&meta_sessionclose_lock, NULL) != 0) {
(void) pthread_rwlock_destroy(&meta_sessionlist_lock);
return (CKR_FUNCTION_FAILED);
}
meta_sessionlist_head = NULL;
num_meta_sessions = 0;
num_rw_meta_sessions = 0;
return (CKR_OK);
}
/*
* meta_sessionManager_finalize
*
* Close all sessions, and destroy all the locks
*/
void
meta_sessionManager_finalize()
{
/*
* Close any remaining metasessions, can just simply call
* meta_CloseAllSessions. The METASLOT_SLOTID argument is
* not used, but need to be passed in.
*/
(void) meta_CloseAllSessions(METASLOT_SLOTID);
(void) pthread_rwlock_destroy(&meta_sessionclose_lock);
(void) pthread_rwlock_destroy(&meta_sessionlist_lock);
}
/*
* meta_handle2session
*
* Convert a CK_SESSION_HANDLE to the corresponding metasession. If
* successful, a write-lock on the session will be held to indicate
* that it's in use. Call REFRELEASE() when finished.
*
*/
CK_RV
meta_handle2session(CK_SESSION_HANDLE hSession, meta_session_t **session)
{
meta_session_t *tmp_session = (meta_session_t *)(hSession);
/* Check for bad args (eg CK_INVALID_HANDLE, which is 0/NULL). */
if (tmp_session == NULL ||
tmp_session->magic_marker != METASLOT_SESSION_MAGIC) {
return (CKR_SESSION_HANDLE_INVALID);
}
/*
* sessions can only be used by a single thread at a time.
* So, we need to get a write-lock.
*/
(void) pthread_rwlock_wrlock(&tmp_session->session_lock);
/* Make sure this session is not in the process of being deleted */
(void) pthread_mutex_lock(&tmp_session->isClosingSession_lock);
if (tmp_session->isClosingSession) {
(void) pthread_mutex_unlock(
&tmp_session->isClosingSession_lock);
(void) pthread_rwlock_unlock(&tmp_session->session_lock);
return (CKR_SESSION_HANDLE_INVALID);
}
(void) pthread_mutex_unlock(&tmp_session->isClosingSession_lock);
*session = tmp_session;
return (CKR_OK);
}
/*
* meta_session_alloc
*/
CK_RV
meta_session_alloc(meta_session_t **session)
{
meta_session_t *new_session;
/* Allocate memory for the session. */
new_session = calloc(1, sizeof (meta_session_t));
if (new_session == NULL)
return (CKR_HOST_MEMORY);
(new_session->mech_support_info).supporting_slots
= malloc(meta_slotManager_get_slotcount() * sizeof (mechinfo_t *));
if ((new_session->mech_support_info).supporting_slots == NULL) {
free(new_session);
return (CKR_HOST_MEMORY);
}
(new_session->mech_support_info).num_supporting_slots = 0;
new_session->magic_marker = METASLOT_SESSION_MAGIC;
(void) pthread_rwlock_init(&new_session->session_lock, NULL);
(void) pthread_mutex_init(&new_session->isClosingSession_lock, NULL);
(void) pthread_rwlock_init(&new_session->object_list_lock, NULL);
*session = new_session;
return (CKR_OK);
}
/*
* meta_session_activate
*
* Create and add a session to the list of active meta sessions.
*/
CK_RV
meta_session_activate(meta_session_t *session)
{
CK_RV rv = CKR_OK;
/* Add session to the list of sessions. */
(void) pthread_rwlock_wrlock(&meta_sessionlist_lock);
INSERT_INTO_LIST(meta_sessionlist_head, session);
(void) pthread_rwlock_unlock(&meta_sessionlist_lock);
return (rv);
}
/*
* meta_session_deactivate
*
*
*/
CK_RV
meta_session_deactivate(meta_session_t *session,
boolean_t have_sessionlist_lock)
{
boolean_t isLastSession = B_FALSE;
meta_object_t *object;
/* Safely resolve attempts of concurrent-close */
(void) pthread_mutex_lock(&session->isClosingSession_lock);
if (session->isClosingSession) {
/* Lost a delete race. */
(void) pthread_mutex_unlock(&session->isClosingSession_lock);
REFRELEASE(session);
return (CKR_SESSION_HANDLE_INVALID);
}
session->isClosingSession = B_TRUE;
session->magic_marker = METASLOT_SESSION_BADMAGIC;
(void) pthread_mutex_unlock(&session->isClosingSession_lock);
/*
* Remove session from the session list. Once removed, it will not
* be possible for another thread to begin using the session.
*/
(void) pthread_rwlock_wrlock(&meta_sessionclose_lock);
if (!have_sessionlist_lock) {
(void) pthread_rwlock_wrlock(&meta_sessionlist_lock);
}
REMOVE_FROM_LIST(meta_sessionlist_head, session);
if (meta_sessionlist_head == NULL) {
isLastSession = B_TRUE;
}
if (!have_sessionlist_lock) {
(void) pthread_rwlock_unlock(&meta_sessionlist_lock);
}
(void) pthread_rwlock_unlock(&meta_sessionclose_lock);
(void) pthread_rwlock_unlock(&session->session_lock);
/* Cleanup any in-progress operations. */
if (session->op1.type != 0) {
meta_operation_cleanup(session, session->op1.type, FALSE);
}
if (session->op1.session != NULL) {
meta_release_slot_session(session->op1.session);
session->op1.session = NULL;
}
/* Remove all the session metaobjects created in this session. */
/* Basically, emulate C_DestroyObject, including safety h2s */
while ((object = session->object_list_head) != NULL) {
CK_RV rv;
rv = meta_handle2object((CK_OBJECT_HANDLE)object, &object);
if (rv != CKR_OK) {
/* Can only happen if someone else just closed it. */
continue;
}
rv = meta_object_deactivate(object, B_FALSE, B_TRUE);
if (rv != CKR_OK) {
continue;
}
rv = meta_object_dealloc(NULL, object, B_FALSE);
if (rv != CKR_OK) {
continue;
}
}
if ((isLastSession) && (metaslot_logged_in())) {
slot_session_t *slotsessp;
CK_RV rv;
rv = meta_get_slot_session(get_keystore_slotnum(), &slotsessp,
session->session_flags);
if (rv != CKR_OK)
return (rv);
rv = FUNCLIST(slotsessp->fw_st_id)->C_Logout(
slotsessp->hSession);
meta_release_slot_session(slotsessp);
/* if C_Logout fails, just ignore the error */
metaslot_set_logged_in_flag(B_FALSE);
if (rv != CKR_OK)
return (rv);
/* need to deactivate all the PRIVATE token objects */
rv = meta_token_object_deactivate(PRIVATE_TOKEN);
if (rv != CKR_OK) {
return (rv);
}
}
return (CKR_OK);
}
/*
* meta_session_dealloc
*
* Release the resources held by a metasession. If the session has been
* activated, it must be deactivated first.
*/
void
meta_session_dealloc(meta_session_t *session)
{
if ((session->find_objs_info).matched_objs) {
free((session->find_objs_info).matched_objs);
}
free((session->mech_support_info).supporting_slots);
/*
* If there were active operations, cleanup the slot session so that
* it can be reused (otherwise provider might complain that an
* operation is active).
*/
if (session->op1.type != 0)
meta_operation_cleanup(session, session->op1.type, FALSE);
/* Final object cleanup. */
(void) pthread_rwlock_destroy(&session->session_lock);
(void) pthread_mutex_destroy(&session->isClosingSession_lock);
(void) pthread_rwlock_destroy(&session->object_list_lock);
meta_session_delay_free(session);
}
/*
* This function adds the to-be-freed meta session to a linked list.
* When the number of sessions queued in the linked list reaches the
* maximum threshold MAX_SESSION_TO_BE_FREED, it will free the first
* session (FIFO) in the list.
*/
void
meta_session_delay_free(meta_session_t *sp)
{
meta_session_t *tmp;
(void) pthread_mutex_lock(&ses_delay_freed.ses_to_be_free_mutex);
/* Add the newly deleted session at the end of the list */
sp->next = NULL;
if (ses_delay_freed.first == NULL) {
ses_delay_freed.last = sp;
ses_delay_freed.first = sp;
} else {
ses_delay_freed.last->next = sp;
ses_delay_freed.last = sp;
}
if (++ses_delay_freed.count >= MAX_SESSION_TO_BE_FREED) {
/*
* Free the first session in the list only if
* the total count reaches maximum threshold.
*/
ses_delay_freed.count--;
tmp = ses_delay_freed.first->next;
free(ses_delay_freed.first);
ses_delay_freed.first = tmp;
}
(void) pthread_mutex_unlock(&ses_delay_freed.ses_to_be_free_mutex);
}