Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/security/nss/lib/softoken/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 74 kB image not shown  

SSL sdb.c   Sprache: C

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file implements PKCS 11 on top of our existing security modules
 *
 * For more information about PKCS 11 See PKCS 11 Token Inteface Standard.
 *   This implementation has two slots:
 *      slot 1 is our generic crypto support. It does not require login.
 *   It supports Public Key ops, and all they bulk ciphers and hashes.
 *   It can also support Private Key ops for imported Private keys. It does
 *   not have any token storage.
 *      slot 2 is our private key support. It requires a login before use. It
 *   can store Private Keys and Certs as token objects. Currently only private
 *   keys and their associated Certificates are saved on the token.
 *
 *   In this implementation, session objects are only visible to the session
 *   that created or generated them.
 */


#include "sdb.h"
#include "pkcs11t.h"
#include "seccomon.h"
#include <sqlite3.h>
#include "prthread.h"
#include "prio.h"
#include <stdio.h>
#include "secport.h"
#include "prmon.h"
#include "prenv.h"
#include "prprf.h"
#include "prsystem.h" /* for PR_GetDirectorySeparator() */
#include <sys/stat.h>
#if defined(_WIN32)
#include <io.h>
#include <windows.h>
#elif defined(XP_UNIX)
#include <unistd.h>
#endif
#if defined(LINUX) && !defined(ANDROID)
#include <linux/magic.h>
#include <sys/vfs.h>
#endif
#include "utilpars.h"

#ifdef SQLITE_UNSAFE_THREADS
#include "prlock.h"
/*
 * SQLite can be compiled to be thread safe or not.
 * turn on SQLITE_UNSAFE_THREADS if the OS does not support
 * a thread safe version of sqlite.
 */

static PRLock *sqlite_lock = NULL;

#define LOCK_SQLITE() PR_Lock(sqlite_lock);
#define UNLOCK_SQLITE() PR_Unlock(sqlite_lock);
#else
#define LOCK_SQLITE()
#define UNLOCK_SQLITE()
#endif

typedef enum {
    SDB_CERT = 1,
    SDB_KEY = 2
} sdbDataType;

/*
 * defines controlling how long we wait to acquire locks.
 *
 * SDB_SQLITE_BUSY_TIMEOUT specifies how long (in milliseconds)
 *  sqlite will wait on lock. If that timeout expires, sqlite will
 *  return SQLITE_BUSY.
 * SDB_BUSY_RETRY_TIME specifies how many seconds the sdb_ code waits
 *  after receiving a busy before retrying.
 * SDB_MAX_BUSY_RETRIES specifies how many times the sdb_ will retry on
 *  a busy condition.
 *
 * SDB_SQLITE_BUSY_TIMEOUT affects all opertions, both manual
 *   (prepare/step/reset/finalize) and automatic (sqlite3_exec()).
 * SDB_BUSY_RETRY_TIME and SDB_MAX_BUSY_RETRIES only affect manual operations
 *
 * total wait time for automatic operations:
 *   1 second (SDB_SQLITE_BUSY_TIMEOUT/1000).
 * total wait time for manual operations:
 *   (1 second + SDB_BUSY_RETRY_TIME) * 30 = 30 seconds.
 * (SDB_SQLITE_BUSY_TIMEOUT/1000 + SDB_BUSY_RETRY_TIME)*SDB_MAX_BUSY_RETRIES
 */

#define SDB_SQLITE_BUSY_TIMEOUT 1000 /* milliseconds */
#define SDB_BUSY_RETRY_TIME 5        /* 'ticks', varies by platforms */
#define SDB_MAX_BUSY_RETRIES 30

/*
 * known attributes
 */

static const CK_ATTRIBUTE_TYPE known_attributes[] = {
    CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION,
    CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER,
    CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED,
    CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL,
    CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY,
    CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE,
    CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER,
    CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE,
    CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT,
    CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT,
    CKA_PUBLIC_KEY_INFO, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS,
    CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE,
    CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE,
    CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS,
    CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS,
    CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_HW_FEATURE_TYPE,
    CKA_RESET_ON_INIT, CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y,
    CKA_RESOLUTION, CKA_CHAR_ROWS, CKA_CHAR_COLUMNS, CKA_COLOR,
    CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, CKA_ENCODING_METHODS, CKA_MIME_TYPES,
    CKA_MECHANISM_TYPE, CKA_REQUIRED_CMS_ATTRIBUTES,
    CKA_DEFAULT_CMS_ATTRIBUTES, CKA_SUPPORTED_CMS_ATTRIBUTES,
    CKA_WRAP_TEMPLATE, CKA_UNWRAP_TEMPLATE, CKA_NSS_TRUST, CKA_NSS_URL,
    CKA_NSS_EMAIL, CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP,
    CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES,
    CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED,
    CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC,
    CKA_NSS_OVERRIDE_EXTENSIONS, CKA_NSS_SERVER_DISTRUST_AFTER,
    CKA_NSS_EMAIL_DISTRUST_AFTER, CKA_TRUST_DIGITAL_SIGNATURE,
    CKA_TRUST_NON_REPUDIATION, CKA_TRUST_KEY_ENCIPHERMENT,
    CKA_TRUST_DATA_ENCIPHERMENT, CKA_TRUST_KEY_AGREEMENT,
    CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, CKA_TRUST_SERVER_AUTH,
    CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION,
    CKA_TRUST_IPSEC_END_SYSTEM, CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER,
    CKA_TRUST_TIME_STAMPING, CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH,
    CKA_CERT_MD5_HASH, CKA_NSS_DB
};

static const int known_attributes_size = PR_ARRAY_SIZE(known_attributes);

/*
 * Note on use of sqlReadDB: Only one thread at a time may have an actual
 * operation going on given sqlite3 * database. An operation is defined as
 * the time from a sqlite3_prepare() until the sqlite3_finalize().
 * Multiple sqlite3 * databases can be open and have simultaneous operations
 * going. We use the sqlXactDB for all write operations. This database
 * is only opened when we first create a transaction and closed when the
 * transaction is complete. sqlReadDB is open when we first opened the database
 * and is used for all read operation. It's use is protected by a monitor. This
 * is because an operation can span the use of FindObjectsInit() through the
 * call to FindObjectsFinal(). In the intermediate time it is possible to call
 * other operations like NSC_GetAttributeValue */


struct SDBPrivateStr {
    char *sqlDBName;                /* invariant, path to this database */
    sqlite3 *sqlXactDB;             /* access protected by dbMon, use protected
                                     * by the transaction. Current transaction db*/

    PRThread *sqlXactThread;        /* protected by dbMon,
                                     * current transaction thread */

    sqlite3 *sqlReadDB;             /* use protected by dbMon, value invariant */
    PRIntervalTime lastUpdateTime;  /* last time the cache was updated */
    PRIntervalTime updateInterval;  /* how long the cache can go before it
                                     * must be updated again */

    sdbDataType type;               /* invariant, database type */
    char *table;                    /* invariant, SQL table which contains the db */
    char *cacheTable;               /* invariant, SQL table cache of db */
    PRMonitor *dbMon;               /* invariant, monitor to protect
                                     * sqlXact* fields, and use of the sqlReadDB */

    CK_ATTRIBUTE_TYPE *schemaAttrs; /* Attribute columns that exist in the table. */
    unsigned int numSchemaAttrs;
};

typedef struct SDBPrivateStr SDBPrivate;

/* Magic for an explicit NULL. NOTE: ideally this should be
 * out of band data. Since it's not completely out of band, pick
 * a value that has no meaning to any existing PKCS #11 attributes.
 * This value is 1) not a valid string (imbedded '\0'). 2) not a U_LONG
 * or a normal key (too short). 3) not a bool (too long). 4) not an RSA
 * public exponent (too many bits).
 */

const unsigned char SQLITE_EXPLICIT_NULL[] = { 0xa5, 0x0, 0x5a };
#define SQLITE_EXPLICIT_NULL_LEN 3

/*
 * determine when we've completed our tasks
 */

static int
sdb_done(int err, int *count)
{
    /* allow as many rows as the database wants to give */
    if (err == SQLITE_ROW) {
        *count = 0;
        return 0;
    }
    if (err != SQLITE_BUSY) {
        return 1;
    }
    /* err == SQLITE_BUSY, Dont' retry forever in this case */
    if (++(*count) >= SDB_MAX_BUSY_RETRIES) {
        return 1;
    }
    return 0;
}

#if defined(_WIN32)
/*
 * NSPR functions and narrow CRT functions do not handle UTF-8 file paths that
 * sqlite3 expects.
 */


static int
sdb_chmod(const char *filename, int pmode)
{
    int result;

    if (!filename) {
        return -1;
    }

    wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename);
    if (!filenameWide) {
        return -1;
    }
    result = _wchmod(filenameWide, pmode);
    PORT_Free(filenameWide);

    return result;
}
#else
#define sdb_chmod(filename, pmode) chmod((filename), (pmode))
#endif

/*
 * find out where sqlite stores the temp tables. We do this by replicating
 * the logic from sqlite.
 */

#if defined(_WIN32)
static char *
sdb_getFallbackTempDir(void)
{
    /* sqlite uses sqlite3_temp_directory if it is not NULL. We don't have
     * access to sqlite3_temp_directory because it is not exported from
     * sqlite3.dll. Assume sqlite3_win32_set_directory isn't called and
     * sqlite3_temp_directory is NULL.
     */

    char path[MAX_PATH];
    DWORD rv;
    size_t len;

    rv = GetTempPathA(MAX_PATH, path);
    if (rv > MAX_PATH || rv == 0)
        return NULL;
    len = strlen(path);
    if (len == 0)
        return NULL;
    /* The returned string ends with a backslash, for example, "C:\TEMP\". */
    if (path[len - 1] == '\\')
        path[len - 1] = '\0';
    return PORT_Strdup(path);
}
#elif defined(XP_UNIX)
static char *
sdb_getFallbackTempDir(void)
{
    const char *azDirs[] = {
        NULL,
        NULL,
        "/var/tmp",
        "/usr/tmp",
        "/tmp",
        NULL /* List terminator */
    };
    unsigned int i;
    struct stat buf;
    const char *zDir = NULL;

    azDirs[0] = sqlite3_temp_directory;
    azDirs[1] = PR_GetEnvSecure("TMPDIR");

    for (i = 0; i < PR_ARRAY_SIZE(azDirs); i++) {
        zDir = azDirs[i];
        if (zDir == NULL)
            continue;
        if (stat(zDir, &buf))
            continue;
        if (!S_ISDIR(buf.st_mode))
            continue;
        if (access(zDir, 07))
            continue;
        break;
    }

    if (zDir == NULL)
        return NULL;
    return PORT_Strdup(zDir);
}
#else
#error "sdb_getFallbackTempDir not implemented"
#endif

#ifndef SQLITE_FCNTL_TEMPFILENAME
/* SQLITE_FCNTL_TEMPFILENAME was added in SQLite 3.7.15 */
#define SQLITE_FCNTL_TEMPFILENAME 16
#endif

static char *
sdb_getTempDir(sqlite3 *sqlDB)
{
    int sqlrv;
    char *result = NULL;
    char *tempName = NULL;
    char *foundSeparator = NULL;

    /* Obtain temporary filename in sqlite's directory for temporary tables */
    sqlrv = sqlite3_file_control(sqlDB, 0, SQLITE_FCNTL_TEMPFILENAME,
                                 (void *)&tempName);
    if (sqlrv == SQLITE_NOTFOUND) {
        /* SQLITE_FCNTL_TEMPFILENAME not implemented because we are using
         * an older SQLite. */

        return sdb_getFallbackTempDir();
    }
    if (sqlrv != SQLITE_OK) {
        return NULL;
    }

    /* We'll extract the temporary directory from tempName */
    foundSeparator = PORT_Strrchr(tempName, PR_GetDirectorySeparator());
    if (foundSeparator) {
        /* We shorten the temp filename string to contain only
         * the directory name (including the trailing separator).
         * We know the byte after the foundSeparator position is
         * safe to use, in the shortest scenario it contains the
         * end-of-string byte.
         * By keeping the separator at the found position, it will
         * even work if tempDir consists of the separator, only.
         * (In this case the toplevel directory will be used for
         * access speed testing). */

        ++foundSeparator;
        *foundSeparator = 0;

        /* Now we copy the directory name for our caller */
        result = PORT_Strdup(tempName);
    }

    sqlite3_free(tempName);
    return result;
}

/*
 * Map SQL_LITE errors to PKCS #11 errors as best we can.
 */

static CK_RV
sdb_mapSQLError(sdbDataType type, int sqlerr)
{
    switch (sqlerr) {
        /* good matches */
        case SQLITE_OK:
        case SQLITE_DONE:
            return CKR_OK;
        case SQLITE_NOMEM:
            return CKR_HOST_MEMORY;
        case SQLITE_READONLY:
            return CKR_TOKEN_WRITE_PROTECTED;
        /* close matches */
        case SQLITE_AUTH:
        case SQLITE_PERM:
        /*return CKR_USER_NOT_LOGGED_IN; */
        case SQLITE_CANTOPEN:
        case SQLITE_NOTFOUND:
            /* NSS distiguishes between failure to open the cert and the key db */
            return type == SDB_CERT ? CKR_NSS_CERTDB_FAILED : CKR_NSS_KEYDB_FAILED;
        case SQLITE_IOERR:
            return CKR_DEVICE_ERROR;
        default:
            break;
    }
    return CKR_GENERAL_ERROR;
}

/*
 * build up database name from a directory, prefix, name, version and flags.
 */

static char *
sdb_BuildFileName(const char *directory,
                  const char *prefix, const char *type,
                  int version)
{
    char *dbname = NULL;
    /* build the full dbname */
    dbname = sqlite3_mprintf("%s%c%s%s%d.db", directory,
                             (int)(unsigned char)PR_GetDirectorySeparator(),
                             prefix, type, version);
    return dbname;
}

/*
 * find out how expensive the access system call is for non-existant files
 * in the given directory.  Return the number of operations done in 33 ms.
 */

static PRUint32
sdb_measureAccess(const char *directory)
{
    PRUint32 i;
    PRIntervalTime time;
    PRIntervalTime delta;
    PRIntervalTime duration = PR_MillisecondsToInterval(33);
    const char *doesntExistName = "_dOeSnotExist_.db";
    char *temp, *tempStartOfFilename;
    size_t maxTempLen, maxFileNameLen, directoryLength, tmpdirLength = 0;
#ifdef SDB_MEASURE_USE_TEMP_DIR
    /*
     * on some OS's and Filesystems, creating a bunch of files and deleting
     * them messes up the systems's caching, but if we create the files in
     * a temp directory which we later delete, then the cache gets cleared
     * up. This code uses several OS dependent calls, and it's not clear
     * that temp directory use won't mess up other filesystems and OS caching,
     * so if you need this for your OS, you can turn on the
     * 'SDB_MEASURE_USE_TEMP_DIR' define in coreconf
     */

    const char template[] = "dbTemp.XXXXXX";
    tmpdirLength = sizeof(template);
#endif
    /* no directory, just return one */
    if (directory == NULL) {
        return 1;
    }

    /* our calculation assumes time is a 4 bytes == 32 bit integer */
    PORT_Assert(sizeof(time) == 4);

    directoryLength = strlen(directory);

    maxTempLen = directoryLength + 1       /* dirname + / */
                 + tmpdirLength            /* tmpdirname includes / */
                 + strlen(doesntExistName) /* filename base */
                 + 11                      /* max chars for 32 bit int plus potential sign */
                 + 1;                      /* zero terminator */

    temp = PORT_ZAlloc(maxTempLen);
    if (!temp) {
        return 1;
    }

    /* We'll copy directory into temp just once, then ensure it ends
     * with the directory separator. */


    strcpy(temp, directory);
    if (directory[directoryLength - 1] != PR_GetDirectorySeparator()) {
        temp[directoryLength++] = PR_GetDirectorySeparator();
    }

#ifdef SDB_MEASURE_USE_TEMP_DIR
    /* add the template for a temporary subdir, and create it */
    strcat(temp, template);
    if (!mkdtemp(temp)) {
        PORT_Free(temp);
        return 1;
    }
    /* and terminate that tmp subdir with a / */
    strcat(temp, "/");
#endif

    /* Remember the position after the last separator, and calculate the
     * number of remaining bytes. */

    tempStartOfFilename = temp + directoryLength + tmpdirLength;
    maxFileNameLen = maxTempLen - directoryLength;

    /* measure number of Access operations that can be done in 33 milliseconds
     * (1/30'th of a second), or 10000 operations, which ever comes first.
     */

    time = PR_IntervalNow();
    for (i = 0; i < 10000u; i++) {
        PRIntervalTime next;

        /* We'll use the variable part first in the filename string, just in
         * case it's longer than assumed, so if anything gets cut off, it
         * will be cut off from the constant part.
         * This code assumes the directory name at the beginning of
         * temp remains unchanged during our loop. */

        PR_snprintf(tempStartOfFilename, maxFileNameLen,
                    ".%lu%s", (PRUint32)(time + i), doesntExistName);
        PR_Access(temp, PR_ACCESS_EXISTS);
        next = PR_IntervalNow();
        delta = next - time;
        if (delta >= duration)
            break;
    }

#ifdef SDB_MEASURE_USE_TEMP_DIR
    /* turn temp back into our tmpdir path by removing doesntExistName, and
     * remove the tmp dir */

    *tempStartOfFilename = '\0';
    (void)rmdir(temp);
#endif
    PORT_Free(temp);

    /* always return 1 or greater */
    return i ? i : 1u;
}

/*
 * some file sytems are very slow to run sqlite3 on, particularly if the
 * access count is pretty high. On these filesystems is faster to create
 * a temporary database on the local filesystem and access that. This
 * code uses a temporary table to create that cache. Temp tables are
 * automatically cleared when the database handle it was created on
 * Is freed.
 */

static const char DROP_CACHE_CMD[] = "DROP TABLE %s";
static const char CREATE_CACHE_CMD[] =
    "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s";
static const char CREATE_ISSUER_INDEX_CMD[] =
    "CREATE INDEX issuer ON %s (a81)";
static const char CREATE_SUBJECT_INDEX_CMD[] =
    "CREATE INDEX subject ON %s (a101)";
static const char CREATE_LABEL_INDEX_CMD[] = "CREATE INDEX label ON %s (a3)";
static const char CREATE_ID_INDEX_CMD[] = "CREATE INDEX ckaid ON %s (a102)";

static CK_RV
sdb_buildCache(sqlite3 *sqlDB, sdbDataType type,
               const char *cacheTable, const char *table)
{
    char *newStr;
    int sqlerr = SQLITE_OK;

    newStr = sqlite3_mprintf(CREATE_CACHE_CMD, cacheTable, table);
    if (newStr == NULL) {
        return CKR_HOST_MEMORY;
    }
    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    if (sqlerr != SQLITE_OK) {
        return sdb_mapSQLError(type, sqlerr);
    }
    /* failure to create the indexes is not an issue */
    newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, cacheTable);
    if (newStr == NULL) {
        return CKR_OK;
    }
    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, cacheTable);
    if (newStr == NULL) {
        return CKR_OK;
    }
    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, cacheTable);
    if (newStr == NULL) {
        return CKR_OK;
    }
    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, cacheTable);
    if (newStr == NULL) {
        return CKR_OK;
    }
    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    return CKR_OK;
}

/*
 * update the cache and the data records describing it.
 *  The cache is updated by dropping the temp database and recreating it.
 */

static CK_RV
sdb_updateCache(SDBPrivate *sdb_p)
{
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    char *newStr;

    /* drop the old table */
    newStr = sqlite3_mprintf(DROP_CACHE_CMD, sdb_p->cacheTable);
    if (newStr == NULL) {
        return CKR_HOST_MEMORY;
    }
    sqlerr = sqlite3_exec(sdb_p->sqlReadDB, newStr, NULL, 0, NULL);
    sqlite3_free(newStr);
    if ((sqlerr != SQLITE_OK) && (sqlerr != SQLITE_ERROR)) {
        /* something went wrong with the drop, don't try to refresh...
         * NOTE: SQLITE_ERROR is returned if the table doesn't exist. In
         * that case, we just continue on and try to reload it */

        return sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    /* set up the new table */
    error = sdb_buildCache(sdb_p->sqlReadDB, sdb_p->type,
                           sdb_p->cacheTable, sdb_p->table);
    if (error == CKR_OK) {
        /* we have a new cache! */
        sdb_p->lastUpdateTime = PR_IntervalNow();
    }
    return error;
}

/*
 *  The sharing of sqlite3 handles across threads is tricky. Older versions
 *  couldn't at all, but newer ones can under strict conditions. Basically
 *  no 2 threads can use the same handle while another thread has an open
 *  stmt running. Once the sqlite3_stmt is finalized, another thread can then
 *  use the database handle.
 *
 *  We use monitors to protect against trying to use a database before
 *  it's sqlite3_stmt is finalized. This is preferable to the opening and
 *  closing the database each operation because there is significant overhead
 *  in the open and close. Also continually opening and closing the database
 *  defeats the cache code as the cache table is lost on close (thus
 *  requiring us to have to reinitialize the cache every operation).
 *
 *  An execption to the shared handle is transations. All writes happen
 *  through a transaction. When we are in  a transaction, we must use the
 *  same database pointer for that entire transation. In this case we save
 *  the transaction database and use it for all accesses on the transaction
 *  thread. Other threads use the common database.
 *
 *  There can only be once active transaction on the database at a time.
 *
 *  sdb_openDBLocal() provides us with a valid database handle for whatever
 *  state we are in (reading or in a transaction), and acquires any locks
 *  appropriate to that state. It also decides when it's time to refresh
 *  the cache before we start an operation. Any database handle returned
 *  just eventually be closed with sdb_closeDBLocal().
 *
 *  The table returned either points to the database's physical table, or
 *  to the cached shadow. Tranactions always return the physical table
 *  and read operations return either the physical table or the cache
 *  depending on whether or not the cache exists.
 */

static CK_RV
sdb_openDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB, const char **table)
{
    *sqlDB = NULL;

    PR_EnterMonitor(sdb_p->dbMon);

    if (table) {
        *table = sdb_p->table;
    }

    /* We're in a transaction, use the transaction DB */
    if ((sdb_p->sqlXactDB) && (sdb_p->sqlXactThread == PR_GetCurrentThread())) {
        *sqlDB = sdb_p->sqlXactDB;
        /* only one thread can get here, safe to unlock */
        PR_ExitMonitor(sdb_p->dbMon);
        return CKR_OK;
    }

    /*
     * if we are just reading from the table, we may have the table
     * cached in a temporary table (especially if it's on a shared FS).
     * In that case we want to see updates to the table, the the granularity
     * is on order of human scale, not computer scale.
     */

    if (table && sdb_p->cacheTable) {
        PRIntervalTime now = PR_IntervalNow();
        if ((now - sdb_p->lastUpdateTime) > sdb_p->updateInterval) {
            sdb_updateCache(sdb_p);
        }
        *table = sdb_p->cacheTable;
    }

    *sqlDB = sdb_p->sqlReadDB;

    /* leave holding the lock. only one thread can actually use a given
     * database connection at once */


    return CKR_OK;
}

/* closing the local database currenly means unlocking the monitor */
static CK_RV
sdb_closeDBLocal(SDBPrivate *sdb_p, sqlite3 *sqlDB)
{
    if (sdb_p->sqlXactDB != sqlDB) {
        /* if we weren't in a transaction, we got a lock */
        PR_ExitMonitor(sdb_p->dbMon);
    }
    return CKR_OK;
}

/*
 * wrapper to sqlite3_open which also sets the busy_timeout
 */

static int
sdb_openDB(const char *name, sqlite3 **sqlDB, int flags)
{
    int sqlerr;
    int openFlags;

    *sqlDB = NULL;

    if (flags & SDB_RDONLY) {
        openFlags = SQLITE_OPEN_READONLY;
    } else {
        openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
        /* sqlite 3.34 seem to incorrectly open readwrite.
         * when the file is readonly. Explicitly reject that issue here */

        if ((_NSSUTIL_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) && (_NSSUTIL_Access(name, PR_ACCESS_WRITE_OK) != PR_SUCCESS)) {
            return SQLITE_READONLY;
        }
    }

    /* Requires SQLite 3.5.0 or newer. */
    sqlerr = sqlite3_open_v2(name, sqlDB, openFlags, NULL);
    if (sqlerr != SQLITE_OK) {
        return sqlerr;
    }

    sqlerr = sqlite3_busy_timeout(*sqlDB, SDB_SQLITE_BUSY_TIMEOUT);
    if (sqlerr != SQLITE_OK) {
        sqlite3_close(*sqlDB);
        *sqlDB = NULL;
        return sqlerr;
    }
    return SQLITE_OK;
}

/* Sigh, if we created a new table since we opened the database,
 * the database handle will not see the new table, we need to close this
 * database and reopen it. Caller must be in a transaction or holding
 * the dbMon. sqlDB is changed on success. */

static int
sdb_reopenDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB)
{
    sqlite3 *newDB;
    int sqlerr;

    /* open a new database */
    sqlerr = sdb_openDB(sdb_p->sqlDBName, &newDB, SDB_RDONLY);
    if (sqlerr != SQLITE_OK) {
        return sqlerr;
    }

    /* if we are in a transaction, we may not be holding the monitor.
     * grab it before we update the transaction database. This is
     * safe since are using monitors. */

    PR_EnterMonitor(sdb_p->dbMon);
    /* update our view of the database */
    if (sdb_p->sqlReadDB == *sqlDB) {
        sdb_p->sqlReadDB = newDB;
    } else if (sdb_p->sqlXactDB == *sqlDB) {
        sdb_p->sqlXactDB = newDB;
    }
    PR_ExitMonitor(sdb_p->dbMon);

    /* close the old one */
    sqlite3_close(*sqlDB);

    *sqlDB = newDB;
    return SQLITE_OK;
}

struct SDBFindStr {
    sqlite3 *sqlDB;
    sqlite3_stmt *findstmt;
};

static const char FIND_OBJECTS_CMD[] = "SELECT ALL id FROM %s WHERE %s;";
static const char FIND_OBJECTS_ALL_CMD[] = "SELECT ALL id FROM %s;";
CK_RV
sdb_FindObjectsInit(SDB *sdb, const CK_ATTRIBUTE *template, CK_ULONG count,
                    SDBFind **find)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    const char *table;
    char *newStr, *findStr = NULL;
    sqlite3_stmt *findstmt = NULL;
    char *join = "";
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    unsigned int i;

    LOCK_SQLITE()
    *find = NULL;
    error = sdb_openDBLocal(sdb_p, &sqlDB, &table);
    if (error != CKR_OK) {
        goto loser;
    }

    findStr = sqlite3_mprintf("");
    for (i = 0; findStr && i < count; i++) {
        newStr = sqlite3_mprintf("%s%sa%x=$DATA%d", findStr, join,
                                 template[i].type, i);
        join = " AND ";
        sqlite3_free(findStr);
        findStr = newStr;
    }

    if (findStr == NULL) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }

    if (count == 0) {
        newStr = sqlite3_mprintf(FIND_OBJECTS_ALL_CMD, table);
    } else {
        newStr = sqlite3_mprintf(FIND_OBJECTS_CMD, table, findStr);
    }
    sqlite3_free(findStr);
    if (newStr == NULL) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }
    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &findstmt, NULL);
    sqlite3_free(newStr);
    for (i = 0; sqlerr == SQLITE_OK && i < count; i++) {
        const void *blobData = template[i].pValue;
        unsigned int blobSize = template[i].ulValueLen;
        if (blobSize == 0) {
            blobSize = SQLITE_EXPLICIT_NULL_LEN;
            blobData = SQLITE_EXPLICIT_NULL;
        }
        sqlerr = sqlite3_bind_blob(findstmt, i + 1, blobData, blobSize,
                                   SQLITE_TRANSIENT);
    }
    if (sqlerr == SQLITE_OK) {
        *find = PORT_New(SDBFind);
        if (*find == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        (*find)->findstmt = findstmt;
        (*find)->sqlDB = sqlDB;
        UNLOCK_SQLITE()
        return CKR_OK;
    }
    error = sdb_mapSQLError(sdb_p->type, sqlerr);

loser:
    if (findstmt) {
        sqlite3_reset(findstmt);
        sqlite3_finalize(findstmt);
    }
    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    UNLOCK_SQLITE()
    return error;
}

CK_RV
sdb_FindObjects(SDB *sdb, SDBFind *sdbFind, CK_OBJECT_HANDLE *object,
                CK_ULONG arraySize, CK_ULONG *count)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3_stmt *stmt = sdbFind->findstmt;
    int sqlerr = SQLITE_OK;
    int retry = 0;

    *count = 0;

    if (arraySize == 0) {
        return CKR_OK;
    }
    LOCK_SQLITE()

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
        if (sqlerr == SQLITE_ROW) {
            /* only care about the id */
            *object++ = sqlite3_column_int(stmt, 0);
            arraySize--;
            (*count)++;
        }
    } while (!sdb_done(sqlerr, &retry) && (arraySize > 0));

    /* we only have some of the objects, there is probably more,
     * set the sqlerr to an OK value so we return CKR_OK */

    if (sqlerr == SQLITE_ROW && arraySize == 0) {
        sqlerr = SQLITE_DONE;
    }
    UNLOCK_SQLITE()

    return sdb_mapSQLError(sdb_p->type, sqlerr);
}

CK_RV
sdb_FindObjectsFinal(SDB *sdb, SDBFind *sdbFind)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3_stmt *stmt = sdbFind->findstmt;
    sqlite3 *sqlDB = sdbFind->sqlDB;
    int sqlerr = SQLITE_OK;

    LOCK_SQLITE()
    if (stmt) {
        sqlite3_reset(stmt);
        sqlerr = sqlite3_finalize(stmt);
    }
    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    PORT_Free(sdbFind);

    UNLOCK_SQLITE()
    return sdb_mapSQLError(sdb_p->type, sqlerr);
}

static CK_RV
sdb_GetValidAttributeValueNoLock(SDB *sdb, CK_OBJECT_HANDLE object_id,
                                 CK_ATTRIBUTE *template, CK_ULONG count)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    const char *table = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int found = 0;
    int retry = 0;
    unsigned int i;

    if (count == 0) {
        error = CKR_OBJECT_HANDLE_INVALID;
        goto loser;
    }

    /* open a new db if necessary */
    error = sdb_openDBLocal(sdb_p, &sqlDB, &table);
    if (error != CKR_OK) {
        goto loser;
    }

    char *columns = NULL;
    for (i = 0; i < count; i++) {
        char *newColumns;
        if (columns) {
            newColumns = sqlite3_mprintf("%s, a%x", columns, template[i].type);
            sqlite3_free(columns);
            columns = NULL;
        } else {
            newColumns = sqlite3_mprintf("a%x"template[i].type);
        }
        if (!newColumns) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        columns = newColumns;
    }

    PORT_Assert(columns);

    char *statement = sqlite3_mprintf("SELECT DISTINCT %s FROM %s where id=$ID LIMIT 1;",
                                      columns, table);
    sqlite3_free(columns);
    columns = NULL;
    if (!statement) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }

    sqlerr = sqlite3_prepare_v2(sqlDB, statement, -1, &stmt, NULL);
    sqlite3_free(statement);
    statement = NULL;
    if (sqlerr != SQLITE_OK) {
        goto loser;
    }

    // NB: indices in sqlite3_bind_int are 1-indexed
    sqlerr = sqlite3_bind_int(stmt, 1, object_id);
    if (sqlerr != SQLITE_OK) {
        goto loser;
    }

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
        if (sqlerr == SQLITE_ROW) {
            PORT_Assert(!found);
            for (i = 0; i < count; i++) {
                unsigned int blobSize;
                const char *blobData;

                // NB: indices in sqlite_column_{bytes,blob} are 0-indexed
                blobSize = sqlite3_column_bytes(stmt, i);
                blobData = sqlite3_column_blob(stmt, i);
                if (blobData == NULL) {
                    /* PKCS 11 requires that get attributes process all the
                     * attributes in the template, marking the attributes with
                     * issues with -1. Mark the error but continue */

                    template[i].ulValueLen = -1;
                    error = CKR_ATTRIBUTE_TYPE_INVALID;
                    continue;
                }
                /* If the blob equals our explicit NULL value, then the
                 * attribute is a NULL. */

                if ((blobSize == SQLITE_EXPLICIT_NULL_LEN) &&
                    (PORT_Memcmp(blobData, SQLITE_EXPLICIT_NULL,
                                 SQLITE_EXPLICIT_NULL_LEN) == 0)) {
                    blobSize = 0;
                }
                if (template[i].pValue) {
                    if (template[i].ulValueLen < blobSize) {
                        /* like CKR_ATTRIBUTE_TYPE_INVALID, continue processing */
                        template[i].ulValueLen = -1;
                        error = CKR_BUFFER_TOO_SMALL;
                        continue;
                    }
                    PORT_Memcpy(template[i].pValue, blobData, blobSize);
                }
                template[i].ulValueLen = blobSize;
            }
            found = 1;
        }
    } while (!sdb_done(sqlerr, &retry));

    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);
    stmt = NULL;

loser:
    /* fix up the error if necessary */
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
        if (!found && error == CKR_OK) {
            error = CKR_OBJECT_HANDLE_INVALID;
        }
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    /* if we had to open a new database, free it now */
    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    return error;
}

/* NOTE: requires sdb_p->schemaAttrs to be sorted asc. */
inline static PRBool
sdb_attributeExists(SDB *sdb, CK_ATTRIBUTE_TYPE attr)
{
    SDBPrivate *sdb_p = sdb->private;
    int first = 0;
    int last = (int)sdb_p->numSchemaAttrs - 1;
    while (last >= first) {
        int mid = first + (last - first) / 2;
        if (sdb_p->schemaAttrs[mid] == attr) {
            return PR_TRUE;
        }
        if (attr > sdb_p->schemaAttrs[mid]) {
            first = mid + 1;
        } else {
            last = mid - 1;
        }
    }

    return PR_FALSE;
}

CK_RV
sdb_GetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id,
                      CK_ATTRIBUTE *template, CK_ULONG count)
{
    CK_RV crv = CKR_OK;
    unsigned int tmplIdx;
    unsigned int resIdx = 0;
    unsigned int validCount = 0;
    unsigned int i;

    if (count == 0) {
        return crv;
    }

    CK_ATTRIBUTE *validTemplate;
    PRBool invalidExists = PR_FALSE;
    for (tmplIdx = 0; tmplIdx < count; tmplIdx++) {
        if (!sdb_attributeExists(sdb, template[tmplIdx].type)) {
            template[tmplIdx].ulValueLen = -1;
            crv = CKR_ATTRIBUTE_TYPE_INVALID;
            invalidExists = PR_TRUE;
            break;
        }
    }

    if (!invalidExists) {
        validTemplate = template;
        validCount = count;
    } else {
        /* Create a new template containing only the valid subset of
         * input |template|, and query with that. */

        validCount = tmplIdx;
        validTemplate = malloc(sizeof(CK_ATTRIBUTE) * count);
        if (!validTemplate) {
            return CKR_HOST_MEMORY;
        }
        /* Copy in what we already know is valid. */
        for (i = 0; i < validCount; i++) {
            validTemplate[i] = template[i];
        }

        /* tmplIdx was left at the index of the first invalid
         * attribute, which has been handled. We only need to
         * deal with the remainder. */

        tmplIdx++;
        for (; tmplIdx < count; tmplIdx++) {
            if (sdb_attributeExists(sdb, template[tmplIdx].type)) {
                validTemplate[validCount++] = template[tmplIdx];
            } else {
                template[tmplIdx].ulValueLen = -1;
            }
        }
    }

    if (validCount) {
        LOCK_SQLITE()
        CK_RV crv2 = sdb_GetValidAttributeValueNoLock(sdb, object_id, validTemplate, validCount);
        UNLOCK_SQLITE()

        /* If an invalid attribute was removed above, let
         * the caller know. Any other error from the actual
         * query should propogate. */

        crv = (crv2 == CKR_OK) ? crv : crv2;
    }

    if (invalidExists) {
        /* Copy out valid lengths. */
        tmplIdx = 0;
        for (resIdx = 0; resIdx < validCount; resIdx++) {
            for (; tmplIdx < count; tmplIdx++) {
                if (template[tmplIdx].type != validTemplate[resIdx].type) {
                    continue;
                }
                template[tmplIdx].ulValueLen = validTemplate[resIdx].ulValueLen;
                tmplIdx++;
                break;
            }
        }
        free(validTemplate);
    }

    return crv;
}

static const char SET_ATTRIBUTE_CMD[] = "UPDATE %s SET %s WHERE id=$ID;";
CK_RV
sdb_SetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id,
                      const CK_ATTRIBUTE *template, CK_ULONG count)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    char *setStr = NULL;
    char *newStr = NULL;
    int sqlerr = SQLITE_OK;
    int retry = 0;
    CK_RV error = CKR_OK;
    unsigned int i;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    if (count == 0) {
        return CKR_OK;
    }

    LOCK_SQLITE()
    setStr = sqlite3_mprintf("");
    for (i = 0; setStr && i < count; i++) {
        if (i == 0) {
            sqlite3_free(setStr);
            setStr = sqlite3_mprintf("a%x=$VALUE%d",
                                     template[i].type, i);
            continue;
        }
        newStr = sqlite3_mprintf("%s,a%x=$VALUE%d", setStr,
                                 template[i].type, i);
        sqlite3_free(setStr);
        setStr = newStr;
    }
    newStr = NULL;

    if (setStr == NULL) {
        return CKR_HOST_MEMORY;
    }
    newStr = sqlite3_mprintf(SET_ATTRIBUTE_CMD, sdb_p->table, setStr);
    sqlite3_free(setStr);
    if (newStr == NULL) {
        UNLOCK_SQLITE()
        return CKR_HOST_MEMORY;
    }
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }
    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
    if (sqlerr != SQLITE_OK)
        goto loser;
    for (i = 0; i < count; i++) {
        if (template[i].ulValueLen != 0) {
            sqlerr = sqlite3_bind_blob(stmt, i + 1, template[i].pValue,
                                       template[i].ulValueLen, SQLITE_STATIC);
        } else {
            sqlerr = sqlite3_bind_blob(stmt, i + 1, SQLITE_EXPLICIT_NULL,
                                       SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC);
        }
        if (sqlerr != SQLITE_OK)
            goto loser;
    }
    sqlerr = sqlite3_bind_int(stmt, i + 1, object_id);
    if (sqlerr != SQLITE_OK)
        goto loser;

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
    } while (!sdb_done(sqlerr, &retry));

loser:
    if (newStr) {
        sqlite3_free(newStr);
    }
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }

    UNLOCK_SQLITE()
    return error;
}

/*
 * check to see if a candidate object handle already exists.
 */

static PRBool
sdb_objectExists(SDB *sdb, CK_OBJECT_HANDLE candidate)
{
    CK_RV crv;
    CK_ATTRIBUTE template = { CKA_LABEL, NULL, 0 };

    crv = sdb_GetValidAttributeValueNoLock(sdb, candidate, &template, 1);
    if (crv == CKR_OBJECT_HANDLE_INVALID) {
        return PR_FALSE;
    }
    return PR_TRUE;
}

/*
 * if we're here, we are in a transaction, so it's safe
 * to examine the current state of the database
 */

static CK_OBJECT_HANDLE
sdb_getObjectId(SDB *sdb)
{
    CK_OBJECT_HANDLE candidate;
    static CK_OBJECT_HANDLE next_obj = CK_INVALID_HANDLE;
    int count;
    /*
     * get an initial object handle to use
     */

    if (next_obj == CK_INVALID_HANDLE) {
        PRTime time;
        time = PR_Now();

        next_obj = (CK_OBJECT_HANDLE)(time & 0x3fffffffL);
    }
    candidate = next_obj++;
    /* detect that we've looped through all the handles... */
    for (count = 0; count < 0x40000000; count++, candidate = next_obj++) {
        /* mask off excess bits */
        candidate &= 0x3fffffff;
        /* if we hit zero, go to the next entry */
        if (candidate == CK_INVALID_HANDLE) {
            continue;
        }
        /* make sure we aren't already using */
        if (!sdb_objectExists(sdb, candidate)) {
            /* this one is free */
            return candidate;
        }
    }

    /* no handle is free, fail */
    return CK_INVALID_HANDLE;
}

CK_RV
sdb_GetNewObjectID(SDB *sdb, CK_OBJECT_HANDLE *object)
{
    CK_OBJECT_HANDLE id;

    id = sdb_getObjectId(sdb);
    if (id == CK_INVALID_HANDLE) {
        return CKR_DEVICE_MEMORY; /* basically we ran out of resources */
    }
    *object = id;
    return CKR_OK;
}

static const char CREATE_CMD[] = "INSERT INTO %s (id%s) VALUES($ID%s);";
CK_RV
sdb_CreateObject(SDB *sdb, CK_OBJECT_HANDLE *object_id,
                 const CK_ATTRIBUTE *template, CK_ULONG count)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    char *columnStr = NULL;
    char *valueStr = NULL;
    char *newStr = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    CK_OBJECT_HANDLE this_object = CK_INVALID_HANDLE;
    int retry = 0;
    unsigned int i;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    LOCK_SQLITE()
    if ((*object_id != CK_INVALID_HANDLE) &&
        !sdb_objectExists(sdb, *object_id)) {
        this_object = *object_id;
    } else {
        this_object = sdb_getObjectId(sdb);
    }
    if (this_object == CK_INVALID_HANDLE) {
        UNLOCK_SQLITE();
        return CKR_HOST_MEMORY;
    }
    columnStr = sqlite3_mprintf("");
    valueStr = sqlite3_mprintf("");
    *object_id = this_object;
    for (i = 0; columnStr && valueStr && i < count; i++) {
        newStr = sqlite3_mprintf("%s,a%x", columnStr, template[i].type);
        sqlite3_free(columnStr);
        columnStr = newStr;
        newStr = sqlite3_mprintf("%s,$VALUE%d", valueStr, i);
        sqlite3_free(valueStr);
        valueStr = newStr;
    }
    newStr = NULL;
    if ((columnStr == NULL) || (valueStr == NULL)) {
        if (columnStr) {
            sqlite3_free(columnStr);
        }
        if (valueStr) {
            sqlite3_free(valueStr);
        }
        UNLOCK_SQLITE()
        return CKR_HOST_MEMORY;
    }
    newStr = sqlite3_mprintf(CREATE_CMD, sdb_p->table, columnStr, valueStr);
    sqlite3_free(columnStr);
    sqlite3_free(valueStr);
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }
    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
    if (sqlerr != SQLITE_OK)
        goto loser;
    sqlerr = sqlite3_bind_int(stmt, 1, *object_id);
    if (sqlerr != SQLITE_OK)
        goto loser;
    for (i = 0; i < count; i++) {
        if (template[i].ulValueLen) {
            sqlerr = sqlite3_bind_blob(stmt, i + 2, template[i].pValue,
                                       template[i].ulValueLen, SQLITE_STATIC);
        } else {
            sqlerr = sqlite3_bind_blob(stmt, i + 2, SQLITE_EXPLICIT_NULL,
                                       SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC);
        }
        if (sqlerr != SQLITE_OK)
            goto loser;
    }

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
    } while (!sdb_done(sqlerr, &retry));

loser:
    if (newStr) {
        sqlite3_free(newStr);
    }
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    UNLOCK_SQLITE()

    return error;
}

/*
 *  Generic destroy that can destroy metadata or objects
 */

static const char DESTROY_CMD[] = "DELETE FROM %s WHERE (id=$ID);";
CK_RV
sdb_destroyAnyObject(SDB *sdb, const char *table,
                     CK_OBJECT_HANDLE object_id, const char *string_id)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    char *newStr = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int retry = 0;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    LOCK_SQLITE()
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }
    newStr = sqlite3_mprintf(DESTROY_CMD, table);
    if (newStr == NULL) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }
    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
    sqlite3_free(newStr);
    if (sqlerr != SQLITE_OK)
        goto loser;
    if (string_id == NULL) {
        sqlerr = sqlite3_bind_int(stmt, 1, object_id);
    } else {
        sqlerr = sqlite3_bind_text(stmt, 1, string_id,
                                   PORT_Strlen(string_id), SQLITE_STATIC);
    }
    if (sqlerr != SQLITE_OK)
        goto loser;

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
    } while (!sdb_done(sqlerr, &retry));

loser:
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }

    UNLOCK_SQLITE()
    return error;
}

CK_RV
sdb_DestroyObject(SDB *sdb, CK_OBJECT_HANDLE object_id)
{
    SDBPrivate *sdb_p = sdb->private;
    return sdb_destroyAnyObject(sdb, sdb_p->table, object_id, NULL);
}

CK_RV
sdb_DestroyMetaData(SDB *sdb, const char *id)
{
    return sdb_destroyAnyObject(sdb, "metaData", 0, id);
}

static const char BEGIN_CMD[] = "BEGIN IMMEDIATE TRANSACTION;";

/*
 * start a transaction.
 *
 * We need to open a new database, then store that new database into
 * the private data structure. We open the database first, then use locks
 * to protect storing the data to prevent deadlocks.
 */

CK_RV
sdb_Begin(SDB *sdb)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int retry = 0;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    LOCK_SQLITE()

    /* get a new version that we will use for the entire transaction */
    sqlerr = sdb_openDB(sdb_p->sqlDBName, &sqlDB, SDB_RDWR);
    if (sqlerr != SQLITE_OK) {
        goto loser;
    }

    sqlerr = sqlite3_prepare_v2(sqlDB, BEGIN_CMD, -1, &stmt, NULL);

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
        /* don't retry BEGIN transaction*/
        retry = 0;
    } while (!sdb_done(sqlerr, &retry));

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

loser:
    error = sdb_mapSQLError(sdb_p->type, sqlerr);

    /* we are starting a new transaction,
     * and if we succeeded, then save this database for the rest of
     * our transaction */

    if (error == CKR_OK) {
        /* we hold a 'BEGIN TRANSACTION' and a sdb_p->lock. At this point
         * sdb_p->sqlXactDB MUST be null */

        PR_EnterMonitor(sdb_p->dbMon);
        PORT_Assert(sdb_p->sqlXactDB == NULL);
        sdb_p->sqlXactDB = sqlDB;
        sdb_p->sqlXactThread = PR_GetCurrentThread();
        PR_ExitMonitor(sdb_p->dbMon);
    } else {
        /* we failed to start our transaction,
         * free any databases we opened. */

        if (sqlDB) {
            sqlite3_close(sqlDB);
        }
    }

    UNLOCK_SQLITE()
    return error;
}

/*
 * Complete a transaction. Basically undo everything we did in begin.
 * There are 2 flavors Abort and Commit. Basically the only differerence between
 * these 2 are what the database will show. (no change in to former, change in
 * the latter).
 */

static CK_RV
sdb_complete(SDB *sdb, const char *cmd)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    sqlite3_stmt *stmt = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int retry = 0;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    /* We must have a transation database, or we shouldn't have arrived here */
    PR_EnterMonitor(sdb_p->dbMon);
    PORT_Assert(sdb_p->sqlXactDB);
    if (sdb_p->sqlXactDB == NULL) {
        PR_ExitMonitor(sdb_p->dbMon);
        return CKR_GENERAL_ERROR; /* shouldn't happen */
    }
    PORT_Assert(sdb_p->sqlXactThread == PR_GetCurrentThread());
    if (sdb_p->sqlXactThread != PR_GetCurrentThread()) {
        PR_ExitMonitor(sdb_p->dbMon);
        return CKR_GENERAL_ERROR; /* shouldn't happen */
    }
    sqlDB = sdb_p->sqlXactDB;
    sdb_p->sqlXactDB = NULL; /* no one else can get to this DB,
                              * safe to unlock */

    sdb_p->sqlXactThread = NULL;
    PR_ExitMonitor(sdb_p->dbMon);

    sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL);

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
    } while (!sdb_done(sqlerr, &retry));

    /* Pending BEGIN TRANSACTIONS Can move forward at this point. */

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    /* we we have a cached DB image, update it as well */
    if (sdb_p->cacheTable) {
        PR_EnterMonitor(sdb_p->dbMon);
        sdb_updateCache(sdb_p);
        PR_ExitMonitor(sdb_p->dbMon);
    }

    error = sdb_mapSQLError(sdb_p->type, sqlerr);

    /* We just finished a transaction.
     * Free the database, and remove it from the list */

    sqlite3_close(sqlDB);

    return error;
}

static const char COMMIT_CMD[] = "COMMIT TRANSACTION;";
CK_RV
sdb_Commit(SDB *sdb)
{
    CK_RV crv;
    LOCK_SQLITE()
    crv = sdb_complete(sdb, COMMIT_CMD);
    UNLOCK_SQLITE()
    return crv;
}

static const char ROLLBACK_CMD[] = "ROLLBACK TRANSACTION;";
CK_RV
sdb_Abort(SDB *sdb)
{
    CK_RV crv;
    LOCK_SQLITE()
    crv = sdb_complete(sdb, ROLLBACK_CMD);
    UNLOCK_SQLITE()
    return crv;
}

static int tableExists(sqlite3 *sqlDB, const char *tableName);

static const char GET_PW_CMD[] = "SELECT ALL * FROM metaData WHERE id=$ID;";
CK_RV
sdb_GetMetaData(SDB *sdb, const char *id, SECItem *item1, SECItem *item2)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = sdb_p->sqlXactDB;
    sqlite3_stmt *stmt = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int found = 0;
    int retry = 0;

    LOCK_SQLITE()
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }

    /* handle 'test' versions of the sqlite db */
    sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL);
    /* Sigh, if we created a new table since we opened the database,
     * the database handle will not see the new table, we need to close this
     * database and reopen it. This is safe because we are holding the lock
     * still. */

    if (sqlerr == SQLITE_SCHEMA) {
        sqlerr = sdb_reopenDBLocal(sdb_p, &sqlDB);
        if (sqlerr != SQLITE_OK) {
            goto loser;
        }
        sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL);
    }
    if (sqlerr != SQLITE_OK)
        goto loser;
    sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC);
    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
        if (sqlerr == SQLITE_ROW) {
            const char *blobData;
            unsigned int len = item1->len;
            item1->len = sqlite3_column_bytes(stmt, 1);
            if (item1->len > len) {
                error = CKR_BUFFER_TOO_SMALL;
                continue;
            }
            blobData = sqlite3_column_blob(stmt, 1);
            PORT_Memcpy(item1->data, blobData, item1->len);
            if (item2) {
                len = item2->len;
                item2->len = sqlite3_column_bytes(stmt, 2);
                if (item2->len > len) {
                    error = CKR_BUFFER_TOO_SMALL;
                    continue;
                }
                blobData = sqlite3_column_blob(stmt, 2);
                PORT_Memcpy(item2->data, blobData, item2->len);
            }
            found = 1;
        }
    } while (!sdb_done(sqlerr, &retry));

loser:
    /* fix up the error if necessary */
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
        if (!found && error == CKR_OK) {
            error = CKR_OBJECT_HANDLE_INVALID;
        }
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    UNLOCK_SQLITE()

    return error;
}

static const char PW_CREATE_TABLE_CMD[] =
    "CREATE TABLE metaData (id PRIMARY KEY UNIQUE ON CONFLICT REPLACE, item1, item2);";
static const char PW_CREATE_CMD[] =
    "INSERT INTO metaData (id,item1,item2) VALUES($ID,$ITEM1,$ITEM2);";
static const char MD_CREATE_CMD[] =
    "INSERT INTO metaData (id,item1) VALUES($ID,$ITEM1);";

CK_RV
sdb_PutMetaData(SDB *sdb, const char *id, const SECItem *item1,
                const SECItem *item2)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = sdb_p->sqlXactDB;
    sqlite3_stmt *stmt = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    int retry = 0;
    const char *cmd = PW_CREATE_CMD;

    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    LOCK_SQLITE()
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }

    if (!tableExists(sqlDB, "metaData")) {
        sqlerr = sqlite3_exec(sqlDB, PW_CREATE_TABLE_CMD, NULL, 0, NULL);
        if (sqlerr != SQLITE_OK)
            goto loser;
    }
    if (item2 == NULL) {
        cmd = MD_CREATE_CMD;
    }
    sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL);
    if (sqlerr != SQLITE_OK)
        goto loser;
    sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC);
    if (sqlerr != SQLITE_OK)
        goto loser;
    sqlerr = sqlite3_bind_blob(stmt, 2, item1->data, item1->len, SQLITE_STATIC);
    if (sqlerr != SQLITE_OK)
        goto loser;
    if (item2) {
        sqlerr = sqlite3_bind_blob(stmt, 3, item2->data,
                                   item2->len, SQLITE_STATIC);
        if (sqlerr != SQLITE_OK)
            goto loser;
    }

    do {
        sqlerr = sqlite3_step(stmt);
        if (sqlerr == SQLITE_BUSY) {
            PR_Sleep(SDB_BUSY_RETRY_TIME);
        }
    } while (!sdb_done(sqlerr, &retry));

loser:
    /* fix up the error if necessary */
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    if (stmt) {
        sqlite3_reset(stmt);
        sqlite3_finalize(stmt);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }
    UNLOCK_SQLITE()

    return error;
}

static const char RESET_CMD[] = "DELETE FROM %s;";
CK_RV
sdb_Reset(SDB *sdb)
{
    SDBPrivate *sdb_p = sdb->private;
    sqlite3 *sqlDB = NULL;
    char *newStr;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;

    /* only Key databases can be reset */
    if (sdb_p->type != SDB_KEY) {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    LOCK_SQLITE()
    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
    if (error != CKR_OK) {
        goto loser;
    }

    if (tableExists(sqlDB, sdb_p->table)) {
        /* delete the contents of the key table */
        newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);

        if (sqlerr != SQLITE_OK)
            goto loser;
    }

    /* delete the password entry table */
    sqlerr = sqlite3_exec(sqlDB, "DROP TABLE IF EXISTS metaData;",
                          NULL, 0, NULL);

loser:
    /* fix up the error if necessary */
    if (error == CKR_OK) {
        error = sdb_mapSQLError(sdb_p->type, sqlerr);
    }

    if (sqlDB) {
        sdb_closeDBLocal(sdb_p, sqlDB);
    }

    UNLOCK_SQLITE()
    return error;
}

CK_RV
sdb_Close(SDB *sdb)
{
    SDBPrivate *sdb_p = sdb->private;
    int sqlerr = SQLITE_OK;
    sdbDataType type = sdb_p->type;

    sqlerr = sqlite3_close(sdb_p->sqlReadDB);
    PORT_Free(sdb_p->sqlDBName);
    if (sdb_p->cacheTable) {
        sqlite3_free(sdb_p->cacheTable);
    }
    if (sdb_p->dbMon) {
        PR_DestroyMonitor(sdb_p->dbMon);
    }
    free(sdb_p->schemaAttrs);
    free(sdb_p);
    free(sdb);
    return sdb_mapSQLError(type, sqlerr);
}

/*
 * functions to support open
 */


static const char CHECK_TABLE_CMD[] = "SELECT ALL * FROM %s LIMIT 0;";

/* return 1 if sqlDB contains table 'tableName */
static int
tableExists(sqlite3 *sqlDB, const char *tableName)
{
    char *cmd = sqlite3_mprintf(CHECK_TABLE_CMD, tableName);
    int sqlerr = SQLITE_OK;

    if (cmd == NULL) {
        return 0;
    }

    sqlerr = sqlite3_exec(sqlDB, cmd, NULL, 0, 0);
    sqlite3_free(cmd);

    return (sqlerr == SQLITE_OK) ? 1 : 0;
}

void
sdb_SetForkState(PRBool forked)
{
    /* XXXright now this is a no-op. The global fork state in the softokn3
     * shared library is already taken care of at the PKCS#11 level.
     * If and when we add fork state to the sqlite shared library and extern
     * interface, we will need to set it and reset it from here */

}

static int
sdb_attributeComparator(const void *a, const void *b)
{
    if (*(CK_ATTRIBUTE_TYPE *)a < *(CK_ATTRIBUTE_TYPE *)b) {
        return -1;
    }
    if (*(CK_ATTRIBUTE_TYPE *)a > *(CK_ATTRIBUTE_TYPE *)b) {
        return 1;
    }
    return 0;
}

/*
 * initialize a single database
 */

static const char INIT_CMD[] =
    "CREATE TABLE %s (id PRIMARY KEY UNIQUE ON CONFLICT ABORT%s)";

CK_RV
sdb_init(char *dbname, char *table, sdbDataType type, int *inUpdate,
         int *newInit, int inFlags, PRUint32 accessOps, SDB **pSdb)
{
    int i;
    char *initStr = NULL;
    char *newStr;
    char *queryStr = NULL;
    int inTransaction = 0;
    SDB *sdb = NULL;
    SDBPrivate *sdb_p = NULL;
    sqlite3 *sqlDB = NULL;
    int sqlerr = SQLITE_OK;
    CK_RV error = CKR_OK;
    char *cacheTable = NULL;
    PRIntervalTime now = 0;
    char *env;
    PRBool enableCache = PR_FALSE;
    PRBool checkFSType = PR_FALSE;
    PRBool measureSpeed = PR_FALSE;
    PRBool create;
    int flags = inFlags & 0x7;

    *pSdb = NULL;
    *inUpdate = 0;

    /* sqlite3 doesn't have a flag to specify that we want to
     * open the database read only. If the db doesn't exist,
     * sqlite3 will always create it.
     */

    LOCK_SQLITE();
    create = (_NSSUTIL_Access(dbname, PR_ACCESS_EXISTS) != PR_SUCCESS);
    if ((flags == SDB_RDONLY) && create) {
        error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
        goto loser;
    }
    sqlerr = sdb_openDB(dbname, &sqlDB, flags);
    if (sqlerr != SQLITE_OK) {
        error = sdb_mapSQLError(type, sqlerr);
        goto loser;
    }

    /*
     * SQL created the file, but it doesn't set appropriate modes for
     * a database.
     *
     * NO NSPR call for chmod? :(
     */

    if (create && sdb_chmod(dbname, 0600) != 0) {
        error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
        goto loser;
    }

    if (flags != SDB_RDONLY) {
        sqlerr = sqlite3_exec(sqlDB, BEGIN_CMD, NULL, 0, NULL);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }
        inTransaction = 1;
    }
    if (!tableExists(sqlDB, table)) {
        *newInit = 1;
        if (flags != SDB_CREATE) {
            error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
            goto loser;
        }
        initStr = sqlite3_mprintf("");
        for (i = 0; initStr && i < known_attributes_size; i++) {
            newStr = sqlite3_mprintf("%s, a%x", initStr, known_attributes[i]);
            sqlite3_free(initStr);
            initStr = newStr;
        }
        if (initStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }

        newStr = sqlite3_mprintf(INIT_CMD, table, initStr);
        sqlite3_free(initStr);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }

        newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, table);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }

        newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, table);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }

        newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, table);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }

        newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, table);
        if (newStr == NULL) {
            error = CKR_HOST_MEMORY;
            goto loser;
        }
        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
        sqlite3_free(newStr);
        if (sqlerr != SQLITE_OK) {
            error = sdb_mapSQLError(type, sqlerr);
            goto loser;
        }
    }
    /*
     * detect the case where we have created the database, but have
     * not yet updated it.
     *
     * We only check the Key database because only the key database has
     * a metaData table. The metaData table is created when a password
     * is set, or in the case of update, when a password is supplied.
     * If no key database exists, then the update would have happened immediately
     * on noticing that the cert database didn't exist (see newInit set above).
     */

    if (type == SDB_KEY && !tableExists(sqlDB, "metaData")) {
        *newInit = 1;
    }

    /* access to network filesystems are significantly slower than local ones
     * for database operations. In those cases we need to create a cached copy
     * of the database in a temporary location on the local disk. SQLITE
     * already provides a way to create a temporary table and initialize it,
     * so we use it for the cache (see sdb_buildCache for how it's done).*/


    /*
     * we decide whether or not to use the cache based on the following input.
     *
     * NSS_SDB_USE_CACHE environment variable is set to anything other than
     *   "yes" or "no" (for instance, "auto"): NSS will measure the performance
     *   of access to the temp database versus the access to the user's
     *   passed-in database location. If the temp database location is
     *   "significantly" faster we will use the cache.
     *
     * NSS_SDB_USE_CACHE environment variable is nonexistent or set to "no":
     *   cache will not be used.
     *
     * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will
     *   always be used.
     *
     * It is expected that most applications will not need this feature, and
     * thus it is disabled by default.
     */


    env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");

    /* Variables enableCache, checkFSType, measureSpeed are PR_FALSE by default,
     * which is the expected behavior for NSS_SDB_USE_CACHE="no".
     * We don't need to check for "no" here. */

    if (!env) {
        /* By default, with no variable set, we avoid expensive measuring for
         * most FS types. We start with inexpensive FS type checking, and
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=85 G=88

¤ Dauer der Verarbeitung: 0.68 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.