AppleDatabase.cpp   [plain text]


/*
 * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
//  AppleDatabase.cpp - Description t.b.d.
//
#include "AppleDatabase.h"
#include <security_cdsa_plugin/DatabaseSession.h>
#include <security_cdsa_plugin/DbContext.h>
#include <security_cdsa_utilities/cssmdb.h>
#include <Security/cssmapple.h>
#include <security_utilities/trackingallocator.h>
#include <security_utilities/logging.h>
#include <fcntl.h>
#include <memory>
#include <libkern/OSAtomic.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <Security/cssmapplePriv.h>
#include <syslog.h>

static const char *kAppleDatabaseChanged = "com.apple.AppleDatabaseChanged";

/* Number of seconds after which we open/pread/close a db to check it's
   version number even if we didn't get any notifications.  Note that we always
   check just after we take a write lock and whenever we get a notification
   that any db on the system has changed. */
static const CFTimeInterval kForceReReadTime = 15.0;

/* Token on which we receive notifications and the pthread_once_t protecting
   it's initialization. */
pthread_once_t gCommonInitMutex = PTHREAD_ONCE_INIT;

/* Global counter of how many notifications we have received and a lock to
   protect the counter. */
static int kSegmentSize = 4;
int32_t* gSegment = NULL;

/* Registration routine for notifcations. Called inside a pthread_once(). */
static void initCommon(void)
{
	// open the file
	int segmentDescriptor = shm_open (kAppleDatabaseChanged, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
	if (segmentDescriptor < 0)
	{
		return;
	}
	
	// set the segment size
	ftruncate (segmentDescriptor, kSegmentSize);
	
	// map it into memory
	int32_t* tmp = (int32_t*) mmap (NULL, kSegmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, segmentDescriptor, 0);
	close (segmentDescriptor);

	if (tmp == (int32_t*) -1) // can't map the memory?
	{
		gSegment = NULL;
	}
	else
	{
		gSegment = tmp;
	}
}

//
// Table
//
Table::Table(const ReadSection &inTableSection) :
	mMetaRecord(inTableSection[OffsetId]),
	mTableSection(inTableSection),
	mRecordsCount(inTableSection[OffsetRecordsCount]),
	mFreeListHead(inTableSection[OffsetFreeListHead]),
	mRecordNumbersCount(inTableSection[OffsetRecordNumbersCount])
{
	// can't easily initialize indexes here, since meta record is incomplete
	// until much later... see DbVersion::open()
}

Table::~Table() 
{
	for_each_map_delete(mIndexMap.begin(), mIndexMap.end());
}

void
Table::readIndexSection()
{
	uint32 indexSectionOffset = mTableSection.at(OffsetIndexesOffset);

	uint32 numIndexes = mTableSection.at(indexSectionOffset + AtomSize);

	for (uint32 i = 0; i < numIndexes; i++) {
		uint32 indexOffset = mTableSection.at(indexSectionOffset + (i + 2) * AtomSize);
		ReadSection indexSection(mTableSection.subsection(indexOffset));
	
		auto_ptr<DbConstIndex> index(new DbConstIndex(*this, indexSection));
		mIndexMap.insert(ConstIndexMap::value_type(index->indexId(), index.get()));
		index.release();
	}
}

Cursor *
Table::createCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion) const
{
	// if an index matches the query, return a cursor which uses the index

	ConstIndexMap::const_iterator it;
	DbQueryKey *queryKey;
	
	for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
		if (it->second->matchesQuery(*inQuery, queryKey)) {
			IndexCursor *cursor = new IndexCursor(queryKey, inDbVersion, *this, it->second);
			return cursor;
		}

	// otherwise, return a cursor that iterates over all table records

	return new LinearCursor(inQuery, inDbVersion, *this);
}

const ReadSection
Table::getRecordSection(uint32 inRecordNumber) const
{
	if (inRecordNumber >= mRecordNumbersCount)
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);

	uint32 aRecordOffset = mTableSection[OffsetRecordNumbers + AtomSize
										 * inRecordNumber];

	// Check if this RecordNumber has been deleted.
	if (aRecordOffset & 1 || aRecordOffset == 0)
		CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

	return MetaRecord::readSection(mTableSection, aRecordOffset);
}

const RecordId
Table::getRecord(const RecordId &inRecordId,
				 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
				 CssmData *inoutData,
				 Allocator &inAllocator) const
{
	const ReadSection aRecordSection = getRecordSection(inRecordId.mRecordNumber);
	const RecordId aRecordId = MetaRecord::unpackRecordId(aRecordSection);

	// Make sure the RecordNumber matches that in the RecordId we just retrived.
	if (aRecordId.mRecordNumber != inRecordId.mRecordNumber)
		CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

	if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);

	// XXX Figure out which value to pass for inQueryFlags (5th) argument
	mMetaRecord.unpackRecord(aRecordSection, inAllocator, inoutAttributes,
							 inoutData, 0);
	return aRecordId;
}

uint32
Table::popFreeList(uint32 &aFreeListHead) const
{
	assert(aFreeListHead | 1);
	uint32 anOffset = aFreeListHead ^ 1;
	uint32 aRecordNumber = (anOffset - OffsetRecordNumbers) / AtomSize;
	aFreeListHead = mTableSection[anOffset];
	return aRecordNumber;
}

const ReadSection
Table::getRecordsSection() const
{
	return mTableSection.subsection(mTableSection[OffsetRecords]);
}

bool
Table::matchesTableId(Id inTableId) const
{
	Id anId = mMetaRecord.dataRecordType();
	if (inTableId == CSSM_DL_DB_RECORD_ANY) // All non schema tables.
		return !(CSSM_DB_RECORDTYPE_SCHEMA_START <= anId
				 && anId < CSSM_DB_RECORDTYPE_SCHEMA_END);

	if (inTableId == CSSM_DL_DB_RECORD_ALL_KEYS) // All key tables.
		return (anId == CSSM_DL_DB_RECORD_PUBLIC_KEY
				|| anId == CSSM_DL_DB_RECORD_PRIVATE_KEY
				|| anId == CSSM_DL_DB_RECORD_SYMMETRIC_KEY);

	return inTableId == anId; // Only if exact match.
}


//
// ModifiedTable
//
ModifiedTable::ModifiedTable(const Table *inTable) :
	mTable(inTable),
	mNewMetaRecord(nil),
	mRecordNumberCount(inTable->recordNumberCount()),
	mFreeListHead(inTable->freeListHead()),
	mIsModified(false)
{
}

ModifiedTable::ModifiedTable(MetaRecord *inMetaRecord) :
	mTable(nil),
	mNewMetaRecord(inMetaRecord),
	mRecordNumberCount(0),
	mFreeListHead(0),
	mIsModified(true)
{
}

ModifiedTable::~ModifiedTable()
{
	for_each_map_delete(mIndexMap.begin(), mIndexMap.end());
	for_each_map_delete(mInsertedMap.begin(), mInsertedMap.end());

	delete mNewMetaRecord;
}

void
ModifiedTable::deleteRecord(const RecordId &inRecordId)
{
	modifyTable();

    uint32 aRecordNumber = inRecordId.mRecordNumber;
	
	// remove the record from all the indexes
	MutableIndexMap::iterator it;
	for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
		it->second->removeRecord(aRecordNumber);

	InsertedMap::iterator anIt = mInsertedMap.find(aRecordNumber);
	if (anIt == mInsertedMap.end())
	{
		// If we have no old table than this record can not exist yet.
		if (!mTable)
			CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

#if RECORDVERSIONCHECK
		const RecordId aRecordId = MetaRecord::unpackRecordId(mTable->getRecordSection(aRecordNumber));
		if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
			CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED);
#endif

		// Schedule the record for deletion
		if (!mDeletedSet.insert(aRecordNumber).second)
			CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // It was already deleted
	}
	else
	{
		const RecordId aRecordId = MetaRecord::unpackRecordId(*anIt->second);
		if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
			CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

#if RECORDVERSIONCHECK
		if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
			CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED);
#endif

		// Remove the inserted (but uncommited) record.  It should already be in mDeletedSet
		// if it existed previously in mTable.
        delete anIt->second;		
		mInsertedMap.erase(anIt);
	}
}

const RecordId
ModifiedTable::insertRecord(uint32 inVersionId,
							const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
							const CssmData *inData)
{
	modifyTable();
	
	auto_ptr<WriteSection> aWriteSection(new WriteSection());
	getMetaRecord().packRecord(*aWriteSection, inAttributes, inData);
    uint32 aRecordNumber = nextRecordNumber();
	
	// add the record to all the indexes; this will throw if the new record
	// violates a unique index
	MutableIndexMap::iterator it;
	for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
		it->second->insertRecord(aRecordNumber, *(aWriteSection.get()));

	// schedule the record for insertion
	RecordId aRecordId(aRecordNumber, inVersionId);
	MetaRecord::packRecordId(aRecordId, *aWriteSection);
    mInsertedMap.insert(InsertedMap::value_type(aRecordNumber, aWriteSection.get()));

    aWriteSection.release();
	
    return aRecordId;
}

const RecordId
ModifiedTable::updateRecord(const RecordId &inRecordId,
							const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
							const CssmData *inData,
							CSSM_DB_MODIFY_MODE inModifyMode)
{
	modifyTable();

    uint32 aRecordNumber = inRecordId.mRecordNumber;
	InsertedMap::iterator anIt = mInsertedMap.find(aRecordNumber);

	// aReUpdate is true iff we are updating an already updated record.
	bool aReUpdate = anIt != mInsertedMap.end();

	// If we are not re-updating and there is no old table than this record does not exist yet.
	if (!aReUpdate && !mTable)
		CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

	const ReadSection &anOldDbRecord = aReUpdate ? *anIt->second : mTable->getRecordSection(aRecordNumber);
	const RecordId aRecordId = MetaRecord::unpackRecordId(anOldDbRecord);

	// Did someone else delete the record we are trying to update.
	if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
		CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

#if RECORDVERSIONCHECK
	// Is the record we that our update is based on current?
	if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
		CssmError::throwMe(CSSMERR_DL_STALE_UNIQUE_RECORD);
#endif

	// Update the actual packed record.
    auto_ptr<WriteSection> aDbRecord(new WriteSection());
	getMetaRecord().updateRecord(anOldDbRecord, *aDbRecord,
		CssmDbRecordAttributeData::overlay(inAttributes), inData, inModifyMode);


	// Bump the RecordVersion of this record.
	RecordId aNewRecordId(aRecordNumber, inRecordId.mCreateVersion, inRecordId.mRecordVersion + 1);
	// Store the RecordVersion in the packed aDbRecord.
	MetaRecord::packRecordId(aNewRecordId, *aDbRecord);

	if (!aReUpdate && !mDeletedSet.insert(aRecordNumber).second)
		CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Record was already in mDeletedSet

	// remove the original record from all the indexes
	MutableIndexMap::iterator it;
	for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
		it->second->removeRecord(aRecordNumber);

	try
	{
		// Add the updated record to all the indexes; this will throw if the new record
		// violates a unique index
		for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
			it->second->insertRecord(aRecordNumber, *(aDbRecord.get()));

		if (aReUpdate)
		{
			// Get rid of anOldDbRecord from the inserted map and replace it
			// with aDbRecord.
			delete anIt->second;
			anIt->second = aDbRecord.get();
		}
		else
		{
			// First time though so let's just put the new value in the map.
			mInsertedMap.insert(InsertedMap::value_type(aRecordNumber, aDbRecord.get()));
		}
		aDbRecord.release();
	}
	catch(...)
	{
		// We only remove aRecordNumber from mDeletedSet if we added it above.
		if (!aReUpdate)
			mDeletedSet.erase(aRecordNumber);

		// The 2 operations below are an attempt to preserve the indices when
		// an insert fails.

		// Remove the updated record from all the indexes
		MutableIndexMap::iterator it;
		for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
			it->second->removeRecord(aRecordNumber);

		// Add the original record back to all the indexes
		for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
			it->second->insertRecord(aRecordNumber, anOldDbRecord);

		throw;
	}

	return aNewRecordId;
}

const RecordId
ModifiedTable::getRecord(const RecordId &inRecordId,
						 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
						 CssmData *inoutData,
						 Allocator &inAllocator) const
{
	if (mIsModified)
	{
		uint32 aRecordNumber = inRecordId.mRecordNumber;
		InsertedMap::const_iterator anIt = mInsertedMap.find(aRecordNumber);
		if (anIt != mInsertedMap.end())
		{
			// We found the record in mInsertedMap so we use the inserted
			// record.
			const ReadSection &aRecordSection = *(anIt->second);
			const RecordId aRecordId = MetaRecord::unpackRecordId(aRecordSection);

			// Make sure the RecordNumber matches that in the RecordId we just retrived.
			if (aRecordId.mRecordNumber != aRecordNumber)
				CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

			if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
				CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);

			// XXX Figure out which value to pass for inQueryFlags (5th) argument
			getMetaRecord().unpackRecord(aRecordSection, inAllocator,
				inoutAttributes, inoutData, 0);

			return aRecordId;
		}
		else if (mDeletedSet.find(aRecordNumber) != mDeletedSet.end())
		{
			 // If aRecordNumber was not in mInsertedMap but it was in
			 // mDeletedSet then it was deleted but not yet commited.
			CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
		}
	}

	if (!mTable)
		CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);

	// Either this table wasn't modified yet or we didn't find aRecordNumber in
	// mInsertedMap nor mDeletedSet so just ask mTable for it.
	return mTable->getRecord(inRecordId, inoutAttributes, inoutData,
		inAllocator);
}

uint32
ModifiedTable::nextRecordNumber()
{
	// If we still have unused free records in mTable get the next one.
	if (mFreeListHead)
		return mTable->popFreeList(mFreeListHead);

	// Bump up the mRecordNumberCount so we don't reuse the same one.
	return mRecordNumberCount++;
}

uint32
ModifiedTable::recordNumberCount() const
{
	uint32 anOldMax = !mTable ? 0 : mTable->recordNumberCount() - 1;
	uint32 anInsertedMax = mInsertedMap.empty() ? 0 : mInsertedMap.rbegin()->first;

	DeletedSet::reverse_iterator anIt = mDeletedSet.rbegin();
	DeletedSet::reverse_iterator anEnd = mDeletedSet.rend();
	for (; anIt != anEnd; anIt++)
	{
		if (*anIt != anOldMax || anOldMax <= anInsertedMax)
			break;
		anOldMax--;
 	}

	return max(anOldMax,anInsertedMax) + 1;
}

const MetaRecord &
ModifiedTable::getMetaRecord() const
{
	return mNewMetaRecord ? *mNewMetaRecord : mTable->getMetaRecord();
}

// prepare to modify the table

void
ModifiedTable::modifyTable()
{
	if (!mIsModified) {
		createMutableIndexes();
		mIsModified = true;
	}
}

// create mutable indexes from the read-only indexes in the underlying table

void
ModifiedTable::createMutableIndexes()
{
	if (mTable == NULL)
		return;
	
	Table::ConstIndexMap::const_iterator it;
	for (it = mTable->mIndexMap.begin(); it != mTable->mIndexMap.end(); it++) {
		auto_ptr<DbMutableIndex> mutableIndex(new DbMutableIndex(*it->second));
		mIndexMap.insert(MutableIndexMap::value_type(it->first, mutableIndex.get()));
		mutableIndex.release();
	}
}

// find, and create if needed, an index with the given id

DbMutableIndex &
ModifiedTable::findIndex(uint32 indexId, const MetaRecord &metaRecord, bool isUniqueIndex)
{
	MutableIndexMap::iterator it = mIndexMap.find(indexId);

	if (it == mIndexMap.end()) {
		// create the new index
		auto_ptr<DbMutableIndex> index(new DbMutableIndex(metaRecord, indexId, isUniqueIndex));
		it = mIndexMap.insert(MutableIndexMap::value_type(indexId, index.get())).first;
		index.release();
	}

	return *it->second;
}

uint32
ModifiedTable::writeIndexSection(WriteSection &tableSection, uint32 offset)
{
	MutableIndexMap::iterator it;
	
	tableSection.put(Table::OffsetIndexesOffset, offset);
	
	// leave room for the size, to be written later
	uint32 indexSectionOffset = offset;
	offset += AtomSize;
	
	offset = tableSection.put(offset, mIndexMap.size());
	
	// leave room for the array of offsets to the indexes
	uint32 indexOffsetOffset = offset;
	offset += mIndexMap.size() * AtomSize;
	
	// write the indexes
	for (it = mIndexMap.begin(); it != mIndexMap.end(); it++) {
		indexOffsetOffset = tableSection.put(indexOffsetOffset, offset);
		offset = it->second->writeIndex(tableSection, offset);
	}
	
	// write the total index section size
	tableSection.put(indexSectionOffset, offset - indexSectionOffset);

	return offset;
}

uint32
ModifiedTable::writeTable(AtomicTempFile &inAtomicTempFile, uint32 inSectionOffset)
{
	if (mTable && !mIsModified) {
		// the table has not been modified, so we can just dump the old table
		// section into the new database

		const ReadSection &tableSection = mTable->getTableSection();
		uint32 tableSize = tableSection.at(Table::OffsetSize);
		
		inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset,
			tableSection.range(Range(0, tableSize)), tableSize);

		return inSectionOffset + tableSize;
	}

	// We should have an old mTable or a mNewMetaRecord but not both.
	assert(mTable != nil ^ mNewMetaRecord != nil);
	const MetaRecord &aNewMetaRecord = getMetaRecord();
	
	uint32 aRecordsCount = 0;
	uint32 aRecordNumbersCount = recordNumberCount();
	uint32 aRecordsOffset = Table::OffsetRecordNumbers + AtomSize * aRecordNumbersCount;
	WriteSection aTableSection(Allocator::standard(), aRecordsOffset);
	aTableSection.size(aRecordsOffset);
	aTableSection.put(Table::OffsetId, aNewMetaRecord.dataRecordType());
	aTableSection.put(Table::OffsetRecords, aRecordsOffset);
	aTableSection.put(Table::OffsetRecordNumbersCount, aRecordNumbersCount);
	
	uint32 anOffset = inSectionOffset + aRecordsOffset;

	if (mTable)
	{
		// XXX Handle schema changes in the future.
		assert(mNewMetaRecord == nil);
		
		// We have a modified old table so copy all non deleted records
		// The code below is rather elaborate, but this is because it attempts
		// to copy large ranges of non deleted records with single calls
		// to AtomicFile::write()
		uint32 anOldRecordsCount = mTable->getRecordsCount();
		ReadSection aRecordsSection = mTable->getRecordsSection();
		uint32 aReadOffset = 0;					// Offset of current record
		uint32 aWriteOffset = aRecordsOffset;	// Offset for current write record
		uint32 aBlockStart = aReadOffset;		// Starting point for read 
		uint32 aBlockSize = 0;					// Size of block to read
		for (uint32 aRecord = 0; aRecord < anOldRecordsCount; aRecord++)
		{
			ReadSection aRecordSection = MetaRecord::readSection(aRecordsSection, aReadOffset);
			uint32 aRecordNumber = MetaRecord::unpackRecordNumber(aRecordSection);
			uint32 aRecordSize = aRecordSection.size();
			aReadOffset += aRecordSize;
			if (mDeletedSet.find(aRecordNumber) == mDeletedSet.end())
			{
				// This record has not been deleted.  Register the offset
				// at which it will be in the new file in aTableSection.
				aTableSection.put(Table::OffsetRecordNumbers
								  + AtomSize * aRecordNumber,
								  aWriteOffset);
				aWriteOffset += aRecordSize;
				aBlockSize += aRecordSize;
				aRecordsCount++;
				// XXX update all indexes being created.
			}
			else
			{
				// The current record has been deleted.  Copy all records up
				// to but not including the current one to the new file.
				if (aBlockSize > 0)
				{
					inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
									   aRecordsSection.range(Range(aBlockStart,
																   aBlockSize)),
									   aBlockSize);
					anOffset += aBlockSize;
				}

				// Set the start of the next block to the start of the next
				// record, and the size of the block to 0.
				aBlockStart = aReadOffset;
				aBlockSize = 0;
			} // if (mDeletedSet..)
		} // for (aRecord...)

		// Copy all records that have not yet been copied to the new file.
		if (aBlockSize > 0)
		{
			inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
							   aRecordsSection.range(Range(aBlockStart,
														   aBlockSize)),
							   aBlockSize);
			anOffset += aBlockSize;
		}
	} // if (mTable)

	// Now add all inserted records to the table.
	InsertedMap::const_iterator anIt = mInsertedMap.begin();
	InsertedMap::const_iterator anEnd = mInsertedMap.end();
	// Iterate over all inserted objects.
	for (; anIt != anEnd; anIt++)
	{
		// Write out each inserted/modified record
		const WriteSection &aRecord = *anIt->second;
		uint32 aRecordNumber = anIt->first;
		// Put offset relative to start of this table in recordNumber array.
		aTableSection.put(Table::OffsetRecordNumbers + AtomSize * aRecordNumber,
						  anOffset - inSectionOffset);
		inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
						   aRecord.address(), aRecord.size());
		anOffset += aRecord.size();
		aRecordsCount++;
		// XXX update all indexes being created.
	}

	// Reconstruct the freelist (this is O(N) where N is the number of recordNumbers)
	// We could implement it faster by using the old freelist and skipping the records
	// that have been inserted.  However building the freelist for the newly used
	// recordNumbers (not in mTable) would look like the code below anyway (starting
	// from mTable->recordNumberCount()).
	// The first part of this would be O(M Log(N))  (where M is the old number of
	// free records, and N is the number of newly inserted records)
	// The second part would be O(N) where N is the currently max RecordNumber
	// in use - the old max RecordNumber in use.
	uint32 aFreeListHead = 0;	// Link to previous free record
	for (uint32 aRecordNumber = 0; aRecordNumber < aRecordNumbersCount; aRecordNumber++)
	{
		// Make the freelist a list of all records with 0 offset (non existing).
		if (!aTableSection.at(Table::OffsetRecordNumbers + AtomSize * aRecordNumber))
		{
			aTableSection.put(Table::OffsetRecordNumbers
								+ AtomSize * aRecordNumber,
								aFreeListHead);
			// Make aFreeListHead point to the previous free recordNumber slot in the table.
			aFreeListHead = (Table::OffsetRecordNumbers + AtomSize * aRecordNumber) | 1;
		}
	}
	aTableSection.put(Table::OffsetFreeListHead, aFreeListHead);

	anOffset -= inSectionOffset;
	
	// Write out indexes, which are part of the table section

	{	
		uint32 indexOffset = anOffset;
		anOffset = writeIndexSection(aTableSection, anOffset);
		inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset + indexOffset,
			aTableSection.address() + indexOffset, anOffset - indexOffset);
	}

	// Set the section size and recordCount.
	aTableSection.put(Table::OffsetSize, anOffset);
	aTableSection.put(Table::OffsetRecordsCount, aRecordsCount);

	// Write out aTableSection header.
	inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset,
					   aTableSection.address(), aTableSection.size());

    return anOffset + inSectionOffset;
}



//
// Metadata
//

// Attribute definitions

static const CSSM_DB_ATTRIBUTE_INFO RelationID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "RelationID"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO RelationName =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "RelationName"},
    CSSM_DB_ATTRIBUTE_FORMAT_STRING
};
static const CSSM_DB_ATTRIBUTE_INFO AttributeID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AttributeID"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO AttributeNameFormat =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AttributeNameFormat"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO AttributeName =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AttributeName"},
    CSSM_DB_ATTRIBUTE_FORMAT_STRING
};
static const CSSM_DB_ATTRIBUTE_INFO AttributeNameID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AttributeNameID"},
    CSSM_DB_ATTRIBUTE_FORMAT_BLOB
};
static const CSSM_DB_ATTRIBUTE_INFO AttributeFormat =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AttributeFormat"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO IndexID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "IndexID"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO IndexType =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "IndexType"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO IndexedDataLocation =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "IndexedDataLocation"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO ModuleID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "ModuleID"},
    CSSM_DB_ATTRIBUTE_FORMAT_BLOB
};
static const CSSM_DB_ATTRIBUTE_INFO AddinVersion =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "AddinVersion"},
    CSSM_DB_ATTRIBUTE_FORMAT_STRING
};
static const CSSM_DB_ATTRIBUTE_INFO SSID =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "SSID"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};
static const CSSM_DB_ATTRIBUTE_INFO SubserviceType =
{
    CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
    {(char*) "SubserviceType"},
    CSSM_DB_ATTRIBUTE_FORMAT_UINT32
};

#define ATTRIBUTE(type, name) \
	{ CSSM_DB_ATTRIBUTE_NAME_AS_STRING, { (char*) #name }, CSSM_DB_ATTRIBUTE_FORMAT_ ## type }
	
static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaRelations[] =
{
	//RelationID, RelationName
	ATTRIBUTE(UINT32, RelationID),
	ATTRIBUTE(STRING, RelationName)
};

static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaAttributes[] =
{
	//RelationID, AttributeID,
    //AttributeNameFormat, AttributeName, AttributeNameID,
    //AttributeFormat
	ATTRIBUTE(UINT32, RelationID),
	ATTRIBUTE(UINT32, AttributeID),
	ATTRIBUTE(UINT32, AttributeNameFormat),
	ATTRIBUTE(STRING, AttributeName),
	ATTRIBUTE(BLOB, AttributeNameID),
	ATTRIBUTE(UINT32, AttributeFormat)
};

static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaIndexes[] =
{
	ATTRIBUTE(UINT32, RelationID),
	ATTRIBUTE(UINT32, IndexID),
	ATTRIBUTE(UINT32, AttributeID),
	ATTRIBUTE(UINT32, IndexType),
	ATTRIBUTE(UINT32, IndexedDataLocation)
    //RelationID, IndexID, AttributeID,
    //IndexType, IndexedDataLocation
};

static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaParsingModule[] =
{
	ATTRIBUTE(UINT32, RelationID),
	ATTRIBUTE(UINT32, AttributeID),
	ATTRIBUTE(BLOB, ModuleID),
	ATTRIBUTE(STRING, AddinVersion),
	ATTRIBUTE(UINT32, SSID),
	ATTRIBUTE(UINT32, SubserviceType)
    //RelationID, AttributeID,
    //ModuleID, AddinVersion, SSID, SubserviceType
};

#undef ATTRIBUTE

//
// DbVersion
//
DbVersion::DbVersion(const AppleDatabase &db, const RefPointer <AtomicBufferedFile> &inAtomicBufferedFile) :
	mDatabase(reinterpret_cast<const uint8 *>(NULL), 0),
	mDb(db),
	mBufferedFile(inAtomicBufferedFile)
{
	off_t aLength = mBufferedFile->length();
	off_t bytesRead = 0;
	const uint8 *ptr = mBufferedFile->read(0, aLength, bytesRead);
	mBufferedFile->close();
	mDatabase = ReadSection(ptr, bytesRead);
	open();
}

DbVersion::~DbVersion()
{
	try
	{
		for_each_map_delete(mTableMap.begin(), mTableMap.end());
	}
	catch(...) {}
}

void
DbVersion::open()
{
	try
	{
		// This is the oposite of DbModifier::commit()
		mVersionId = mDatabase[mDatabase.size() - AtomSize];

		const ReadSection aHeaderSection = mDatabase.subsection(HeaderOffset,
																HeaderSize);
		if (aHeaderSection.at(OffsetMagic) != HeaderMagic)
			CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
	
		// We currently only support one version.  If we support additional
		// file format versions in the future fix this.
		uint32 aVersion = aHeaderSection.at(OffsetVersion);
		if (aVersion != HeaderVersion)
			CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
	
		//const ReadSection anAuthSection =
		//	mDatabase.subsection(HeaderOffset + aHeaderSection.at(OffsetAuthOffset));
		// XXX Do something with anAuthSection.
	
		uint32 aSchemaOffset = aHeaderSection.at(OffsetSchemaOffset);
		const ReadSection aSchemaSection =
			mDatabase.subsection(HeaderOffset + aSchemaOffset);
	
		uint32 aSchemaSize = aSchemaSection[OffsetSchemaSize];
		// Make sure that the given range exists.
		aSchemaSection.subsection(0, aSchemaSize);
		uint32 aTableCount = aSchemaSection[OffsetTablesCount];
	
		// Assert that the size of this section is big enough.
		if (aSchemaSize < OffsetTables + AtomSize * aTableCount)
			CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
	
		for (uint32 aTableNumber = 0; aTableNumber < aTableCount;
			 aTableNumber++)
		{
			uint32 aTableOffset = aSchemaSection.at(OffsetTables + AtomSize
													* aTableNumber);
			// XXX Set the size boundary on aTableSection.
			const ReadSection aTableSection =
				aSchemaSection.subsection(aTableOffset);
			auto_ptr<Table> aTable(new Table(aTableSection));
			Table::Id aTableId = aTable->getMetaRecord().dataRecordType();
			mTableMap.insert(TableMap::value_type(aTableId, aTable.get()));
			aTable.release();
		}

		// Fill in the schema for the meta tables.
		
		findTable(mDb.schemaRelations.DataRecordType).getMetaRecord().
			setRecordAttributeInfo(mDb.schemaRelations);
		findTable(mDb.schemaIndexes.DataRecordType).getMetaRecord().
			setRecordAttributeInfo(mDb.schemaIndexes);
		findTable(mDb.schemaParsingModule.DataRecordType).getMetaRecord().
			setRecordAttributeInfo(mDb.schemaParsingModule);

		// OK, we have created all the tables in the tableMap.  Now
		// lets read the schema and proccess it accordingly.
		// Iterate over all schema records.
		Table &aTable = findTable(mDb.schemaAttributes.DataRecordType);
		aTable.getMetaRecord().setRecordAttributeInfo(mDb.schemaAttributes);
		uint32 aRecordsCount = aTable.getRecordsCount();
		ReadSection aRecordsSection = aTable.getRecordsSection();
		uint32 aReadOffset = 0;
		const MetaRecord &aMetaRecord = aTable.getMetaRecord();

		CSSM_DB_ATTRIBUTE_DATA aRelationIDData =
		{
			RelationID,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aAttributeIDData =
		{
			AttributeID,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aAttributeNameFormatData =
		{
			AttributeNameFormat,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aAttributeNameData =
		{
			AttributeName,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aAttributeNameIDData =
		{
			AttributeNameID,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aAttributeFormatData =
		{
			AttributeFormat,
			0,
			NULL
		};
		CSSM_DB_ATTRIBUTE_DATA aRecordAttributes[] =
		{
			aRelationIDData,
			aAttributeIDData,
			aAttributeNameFormatData,
			aAttributeNameData,
			aAttributeNameIDData,
			aAttributeFormatData
		};
		CSSM_DB_RECORD_ATTRIBUTE_DATA aRecordAttributeData =
		{
			aMetaRecord.dataRecordType(),
			0,
			sizeof(aRecordAttributes) / sizeof(CSSM_DB_ATTRIBUTE_DATA),
			aRecordAttributes
		};
		CssmDbRecordAttributeData &aRecordData = CssmDbRecordAttributeData::overlay(aRecordAttributeData);

		TrackingAllocator recordAllocator(Allocator::standard());
		for (uint32 aRecord = 0; aRecord != aRecordsCount; aRecord++)
		{
			ReadSection aRecordSection = MetaRecord::readSection(aRecordsSection, aReadOffset);
			uint32 aRecordSize = aRecordSection.size();
			aReadOffset += aRecordSize;
			aMetaRecord.unpackRecord(aRecordSection, recordAllocator,
										&aRecordAttributeData, NULL, 0);
			// Create the attribute coresponding to this entry
			if (aRecordData[0].size() != 1 || aRecordData[0].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
				CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
			uint32 aRelationId = aRecordData[0];

			// Skip the schema relations for the meta tables themselves.
			// FIXME: this hard-wires the meta-table relation IDs to be
			// within {CSSM_DB_RECORDTYPE_SCHEMA_START...
			// CSSM_DB_RECORDTYPE_SCHEMA_END} (which is {0..4}). 
			// Bogus - the MDS schema relation IDs start at 
			// CSSM_DB_RELATIONID_MDS_START which is 0x40000000.
			// Ref. Radar 2817921.
			if (CSSM_DB_RECORDTYPE_SCHEMA_START <= aRelationId && aRelationId < CSSM_DB_RECORDTYPE_SCHEMA_END)
				continue;

			// Get the MetaRecord corresponding to the specified RelationId
			MetaRecord &aMetaRecord = findTable(aRelationId).getMetaRecord();

			if (aRecordData[1].size() != 1
				|| aRecordData[1].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
				|| aRecordData[2].size() != 1
				|| aRecordData[2].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
				|| aRecordData[5].size() != 1
				|| aRecordData[5].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
				CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

			uint32 anAttributeId = aRecordData[1];
			uint32 anAttributeNameFormat = aRecordData[2];
			uint32 anAttributeFormat = aRecordData[5];
			auto_ptr<string> aName;
			const CssmData *aNameID = NULL;

			if (aRecordData[3].size() == 1)
			{
				if (aRecordData[3].format() != CSSM_DB_ATTRIBUTE_FORMAT_STRING)
					CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

				auto_ptr<string> aName2(new string(static_cast<string>(aRecordData[3])));
				aName = aName2;
			}

			if (aRecordData[4].size() == 1)
			{
				if (aRecordData[4].format() != CSSM_DB_ATTRIBUTE_FORMAT_BLOB)
					CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

									// @@@ Invoking conversion operator to CssmData & on aRecordData[4]
									// And taking address of result.
				aNameID = &static_cast<const CssmData &>(aRecordData[4]);
			}

			// Make sure that the attribute specified by anAttributeNameFormat is present. 
			switch (anAttributeNameFormat)
			{
			case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
				if (aRecordData[3].size() != 1)
					CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
				break;
			case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
				if (aRecordData[4].size() != 1)
					CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
				break;
			case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
				break;
			default:
				CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
			}

			// Create the attribute
			aMetaRecord.createAttribute(aName.get(), aNameID, anAttributeId, anAttributeFormat);
        }
		
		// initialize the indexes associated with each table
		{
			TableMap::iterator it;
			for (it = mTableMap.begin(); it != mTableMap.end(); it++)
				it->second->readIndexSection();
		}
	}
	catch(...)
	{
		for_each_map_delete(mTableMap.begin(), mTableMap.end());
		mTableMap.clear();
		throw;
	}
}

const RecordId
DbVersion::getRecord(Table::Id inTableId, const RecordId &inRecordId,
							CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
							CssmData *inoutData,
							Allocator &inAllocator) const
{
	return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
										  inoutData, inAllocator);
}

Cursor *
DbVersion::createCursor(const CSSM_QUERY *inQuery) const
{
	// XXX We should add support for these special query types
	// By Creating a Cursor that iterates over multiple tables
	if (!inQuery || inQuery->RecordType == CSSM_DL_DB_RECORD_ANY
		|| inQuery->RecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
	{
		return new MultiCursor(inQuery, *this);
	}

	return findTable(inQuery->RecordType).createCursor(inQuery, *this);
}

bool DbVersion::hasTable(Table::Id inTableId) const
{
    TableMap::const_iterator it = mTableMap.find(inTableId);
	return it != mTableMap.end();
}

const Table &
DbVersion::findTable(Table::Id inTableId) const
{
    TableMap::const_iterator it = mTableMap.find(inTableId);
    if (it == mTableMap.end())
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
	return *it->second;
}

Table &
DbVersion::findTable(Table::Id inTableId)
{
    TableMap::iterator it = mTableMap.find(inTableId);
    if (it == mTableMap.end())
		CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
	return *it->second;
}

//
// Cursor implemetation
//
Cursor::Cursor()
{
}

Cursor::Cursor(const DbVersion &inDbVersion) : mDbVersion(&inDbVersion)
{
}

Cursor::~Cursor()
{
}

bool
Cursor::next(Table::Id &outTableId,
			 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
			 CssmData *outData,
			 Allocator &inAllocator,
			 RecordId &recordId)
{
	return false;
}

//
// LinearCursor implemetation
//
LinearCursor::LinearCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion,
						   const Table &inTable) :
    Cursor(inDbVersion),
    mRecordsCount(inTable.getRecordsCount()),
    mRecord(0),
    mRecordsSection(inTable.getRecordsSection()),
    mReadOffset(0),
    mMetaRecord(inTable.getMetaRecord())
{
	if (inQuery)
	{
	    mConjunctive = inQuery->Conjunctive;
	    mQueryFlags = inQuery->QueryFlags;
	    // XXX Do something with inQuery->QueryLimits?
	    uint32 aPredicatesCount = inQuery->NumSelectionPredicates;
	    mPredicates.resize(aPredicatesCount);
		try
		{
			for (uint32 anIndex = 0; anIndex < aPredicatesCount; anIndex++)
			{
				CSSM_SELECTION_PREDICATE &aPredicate = inQuery->SelectionPredicate[anIndex];
				mPredicates[anIndex] = new SelectionPredicate(mMetaRecord, aPredicate);
			}
		}
		catch(...)
		{
			for_each_delete(mPredicates.begin(), mPredicates.end());
			throw;
		}
	}
}

LinearCursor::~LinearCursor()
{
	for_each_delete(mPredicates.begin(), mPredicates.end());
}

bool
LinearCursor::next(Table::Id &outTableId,
				   CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
				   CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
{
	while (mRecord++ < mRecordsCount)
	{
		ReadSection aRecordSection = MetaRecord::readSection(mRecordsSection, mReadOffset);
		uint32 aRecordSize = aRecordSection.size();
		mReadOffset += aRecordSize;

        PredicateVector::const_iterator anIt = mPredicates.begin();
        PredicateVector::const_iterator anEnd = mPredicates.end();
		bool aMatch;
		if (anIt == anEnd)
		{
			// If there are no predicates we have a match.
			aMatch = true;
		}
		else if (mConjunctive == CSSM_DB_OR)
		{
			// If mConjunctive is OR, the first predicate that returns
			// true indicates a match. Dropthough means no match
			aMatch = false;
			for (; anIt != anEnd; anIt++)
			{
				if ((*anIt)->evaluate(aRecordSection))
				{
					aMatch = true;
                    break;
				}
			}
		}
		else if (mConjunctive == CSSM_DB_AND || mConjunctive == CSSM_DB_NONE)
		{
			// If mConjunctive is AND (or NONE), the first predicate that returns
			// false indicates a mismatch. Dropthough means a match
			aMatch = true;
			for (; anIt != anEnd; anIt++)
			{
				if (!(*anIt)->evaluate(aRecordSection))
				{
					aMatch = false;
                    break;
				}
			}
		}
		else
		{
			// XXX Should be CSSMERR_DL_INVALID_QUERY (or CSSMERR_DL_INVALID_CONJUNTIVE).
			CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_QUERY);
		}

        if (aMatch)
        {
            // Get the actual record.
            mMetaRecord.unpackRecord(aRecordSection, inAllocator,
									 inoutAttributes, inoutData,
									 mQueryFlags);
			outTableId = mMetaRecord.dataRecordType();
			recordId = MetaRecord::unpackRecordId(aRecordSection);
			return true;
        }
    }

	return false;
}

//
// IndexCursor
// 

IndexCursor::IndexCursor(DbQueryKey *queryKey, const DbVersion &inDbVersion,
	const Table &table, const DbConstIndex *index) :
	Cursor(inDbVersion), mQueryKey(queryKey), mTable(table), mIndex(index)
{
	index->performQuery(*queryKey, mBegin, mEnd);
}

IndexCursor::~IndexCursor()
{
	// the query key will be deleted automatically, since it's an auto_ptr
}

bool
IndexCursor::next(Table::Id &outTableId,
	CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
	CssmData *outData,
	Allocator &inAllocator, RecordId &recordId)
{
	if (mBegin == mEnd)
		return false;
		
	ReadSection rs = mIndex->getRecordSection(mBegin++);
	const MetaRecord &metaRecord = mTable.getMetaRecord();

	outTableId = metaRecord.dataRecordType();
	metaRecord.unpackRecord(rs, inAllocator, outAttributes, outData, 0);
	
	recordId = MetaRecord::unpackRecordId(rs);
	return true;
}

//
// MultiCursor
//
MultiCursor::MultiCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion) :
    Cursor(inDbVersion), mTableIterator(inDbVersion.begin())
{
	if (inQuery)
		mQuery.reset(new CssmAutoQuery(*inQuery));
	else
	{
		mQuery.reset(new CssmAutoQuery());
		mQuery->recordType(CSSM_DL_DB_RECORD_ANY);
	}
}

MultiCursor::~MultiCursor()
{
}

bool
MultiCursor::next(Table::Id &outTableId,
				  CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
				  CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
{
	for (;;)
	{
		if (!mCursor.get())
		{
			if (mTableIterator == mDbVersion->end())
				return false;

			const Table &aTable = *mTableIterator++;
			if (!aTable.matchesTableId(mQuery->recordType()))
				continue;

			mCursor.reset(aTable.createCursor(mQuery.get(), *mDbVersion));
		}

		if (mCursor->next(outTableId, inoutAttributes, inoutData, inAllocator, recordId))
			return true;
			
		mCursor.reset(NULL);
	}
}


//
// DbModifier
//
DbModifier::DbModifier(AtomicFile &inAtomicFile, const AppleDatabase &db) :
	Metadata(),
	mDbVersion(),
    mAtomicFile(inAtomicFile),
	mDb(db)
{
}

DbModifier::~DbModifier()
{
    try
    {
		for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
		// mAtomicTempFile will do automatic rollback on destruction.
    }
    catch(...) {}
}

const RefPointer<const DbVersion>
DbModifier::getDbVersion(bool force)
{
    StLock<Mutex> _(mDbVersionLock);

    /* Initialize the shared memory file change mechanism */
    pthread_once(&gCommonInitMutex, initCommon);

    /* If we don't have a mDbVersion yet, or we are force to re-read the file
       before a write transaction, or we have received any notifications after
       the last time we read the file, or more than kForceReReadTime seconds
       have passed since the last time we read the file, we open the file and
       check if it has changed. */
    if (!mDbVersion ||
        force ||
		gSegment == NULL ||
        mNotifyCount != *gSegment ||
        CFAbsoluteTimeGetCurrent() > mDbLastRead + kForceReReadTime)
    {
        RefPointer <AtomicBufferedFile> atomicBufferedFile(mAtomicFile.read());
        off_t length = atomicBufferedFile->open();
        /* Record the number of notifications we've seen and when we last
           opened the file.  */
		if (gSegment != NULL)
		{
			mNotifyCount = *gSegment;
		}
		
        mDbLastRead = CFAbsoluteTimeGetCurrent();

        /* If we already have a mDbVersion, let's check if we can reuse it. */
        if (mDbVersion)
        {
            if (length < AtomSize)
                CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);

            off_t bytesRead = 0;
            const uint8 *ptr = atomicBufferedFile->read(length - AtomSize,
                AtomSize, bytesRead);
            ReadSection aVersionSection(ptr, bytesRead);
            uint32 aVersionId = aVersionSection[0];

            /* If the version stamp hasn't changed the old mDbVersion is still
               current. */
            if (aVersionId == mDbVersion->getVersionId())
                return mDbVersion;
        }

	mDbVersion = new DbVersion(mDb, atomicBufferedFile);
    }

    return mDbVersion;
}

void
DbModifier::createDatabase(const CSSM_DBINFO &inDbInfo,
						   const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry,
						   mode_t mode)
{
	// XXX This needs better locking.  There is a possible race condition between
	// two concurrent creators.  Or a writer/creator or a close/create etc.
	if (mAtomicTempFile || !mModifiedTableMap.empty())
		CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);

    mAtomicTempFile = mAtomicFile.create(mode);
	// Set mVersionId to one since this is the first version of the database.
	mVersionId = 1;

	// we need to create the meta tables first, because inserting tables
	// (including the meta tables themselves) relies on them being there
    createTable(new MetaRecord(mDb.schemaRelations));
    createTable(new MetaRecord(mDb.schemaAttributes));
    createTable(new MetaRecord(mDb.schemaIndexes));
    createTable(new MetaRecord(mDb.schemaParsingModule));
	
	// now add the meta-tables' schema to the meta tables themselves
	insertTableSchema(mDb.schemaRelations);
	insertTableSchema(mDb.schemaAttributes);
	insertTableSchema(mDb.schemaIndexes);
	insertTableSchema(mDb.schemaParsingModule);

    if (inInitialAclEntry != NULL)
    {
        //createACL(*inInitialAclEntry);
    }

    if (inDbInfo.NumberOfRecordTypes == 0)
        return;
    if (inDbInfo.RecordAttributeNames == NULL)
        CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
    if (inDbInfo.RecordIndexes == NULL)
        CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_INDEX);
    if (inDbInfo.DefaultParsingModules == NULL)
        CssmError::throwMe(CSSMERR_DL_INVALID_PARSING_MODULE);

    for (uint32 anIndex = 0; anIndex < inDbInfo.NumberOfRecordTypes; anIndex++)
    {
        insertTable(CssmDbRecordAttributeInfo::overlay(inDbInfo.RecordAttributeNames[anIndex]),
					&inDbInfo.RecordIndexes[anIndex],
					&inDbInfo.DefaultParsingModules[anIndex]);
    }
}

void DbModifier::openDatabase()
{
	// No need to do anything on open if we are already writing the database.
	if (!mAtomicTempFile)
		getDbVersion(false);
}

void DbModifier::closeDatabase()
{
	commit(); // XXX Requires write lock.
	StLock<Mutex> _(mDbVersionLock);
	mDbVersion = NULL;
}

void DbModifier::deleteDatabase()
{
	bool isDirty = mAtomicTempFile;
	rollback(); // XXX Requires write lock.
	StLock<Mutex> _(mDbVersionLock);

	// Clean up mModifiedTableMap in case this object gets reused again for
	// a new create.
	for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
	mModifiedTableMap.clear();

	// If the database was dirty and we had no mDbVersion yet then rollback()
	// would have deleted the db.
	if (!isDirty || mDbVersion)
	{
		mDbVersion = NULL;
		mAtomicFile.performDelete();
	}
}

void
DbModifier::modifyDatabase()
{
	if (mAtomicTempFile)
		return;

	try
	{
		mAtomicTempFile = mAtomicFile.write();
		// Now we are holding the write lock make sure we get the latest greatest version of the db.
		// Also set mVersionId to one more that that of the old database.
		mVersionId = getDbVersion(true)->getVersionId() + 1;

		// Never make a database with mVersionId 0 since it makes bad things happen to Jaguar and older systems
		if (mVersionId == 0)
			mVersionId = 1;

		// Remove all old modified tables
		for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
		mModifiedTableMap.clear();

		// Setup the new tables
		DbVersion::TableMap::const_iterator anIt =
			mDbVersion->mTableMap.begin();
		DbVersion::TableMap::const_iterator anEnd =
			mDbVersion->mTableMap.end();
		for (; anIt != anEnd; ++anIt)
		{
			auto_ptr<ModifiedTable> aTable(new ModifiedTable(anIt->second));
			mModifiedTableMap.insert(ModifiedTableMap::value_type(anIt->first,
																  aTable.get()));
			aTable.release();
		}
	}
	catch(...)
	{
		for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
		mModifiedTableMap.clear();
		rollback();
		throw;
	}
}

void
DbModifier::deleteRecord(Table::Id inTableId, const RecordId &inRecordId)
{
	modifyDatabase();
	findTable(inTableId).deleteRecord(inRecordId);
}

const RecordId
DbModifier::insertRecord(Table::Id inTableId,
						 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
						 const CssmData *inData)
{
	modifyDatabase();
	return findTable(inTableId).insertRecord(mVersionId, inAttributes, inData);
}

const RecordId
DbModifier::updateRecord(Table::Id inTableId, const RecordId &inRecordId,
						 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
						 const CssmData *inData,
						 CSSM_DB_MODIFY_MODE inModifyMode)
{
	modifyDatabase();
	return findTable(inTableId).updateRecord(inRecordId, inAttributes, inData, inModifyMode);
}

// Create a table associated with a given metarecord, and add the table
// to the database.

ModifiedTable *
DbModifier::createTable(MetaRecord *inMetaRecord)
{
	auto_ptr<MetaRecord> aMetaRecord(inMetaRecord);
	auto_ptr<ModifiedTable> aModifiedTable(new ModifiedTable(inMetaRecord));
	// Now that aModifiedTable is fully constructed it owns inMetaRecord
	aMetaRecord.release();

	if (!mModifiedTableMap.insert
		(ModifiedTableMap::value_type(inMetaRecord->dataRecordType(),
									  aModifiedTable.get())).second)
	{
		// XXX Should be CSSMERR_DL_DUPLICATE_RECORDTYPE.  Since that
		// doesn't exist we report that the metatable's unique index would
		// no longer be valid
        CssmError::throwMe(CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA);
	}

	return aModifiedTable.release();
}

void
DbModifier::deleteTable(Table::Id inTableId)
{
	modifyDatabase();
    // Can't delete schema tables.
    if (CSSM_DB_RECORDTYPE_SCHEMA_START <= inTableId
		&& inTableId < CSSM_DB_RECORDTYPE_SCHEMA_END)
        CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);

	// Find the ModifiedTable and delete it
    ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
    if (it == mModifiedTableMap.end())
        CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);

    delete it->second;
    mModifiedTableMap.erase(it);
}

uint32
DbModifier::writeAuthSection(uint32 inSectionOffset)
{
	WriteSection anAuthSection;

    // XXX Put real data into the authsection.
	uint32 anOffset = anAuthSection.put(0, 0);
	anAuthSection.size(anOffset);

	mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
					anAuthSection.address(), anAuthSection.size());
    return inSectionOffset + anOffset;
}

uint32
DbModifier::writeSchemaSection(uint32 inSectionOffset)
{
	uint32 aTableCount = mModifiedTableMap.size();
	WriteSection aTableSection(Allocator::standard(),
							   OffsetTables + AtomSize * aTableCount);
	// Set aTableSection to the correct size.
	aTableSection.size(OffsetTables + AtomSize * aTableCount);
	aTableSection.put(OffsetTablesCount, aTableCount);

	uint32 anOffset = inSectionOffset + OffsetTables + AtomSize * aTableCount;
	ModifiedTableMap::const_iterator anIt = mModifiedTableMap.begin();
	ModifiedTableMap::const_iterator anEnd = mModifiedTableMap.end();
	for (uint32 aTableNumber = 0; anIt != anEnd; anIt++, aTableNumber++)
	{
		// Put the offset to the current table relative to the start of
		// this section into the tables array
		aTableSection.put(OffsetTables + AtomSize * aTableNumber,
						  anOffset - inSectionOffset);
		anOffset = anIt->second->writeTable(*mAtomicTempFile, anOffset);
	}

	aTableSection.put(OffsetSchemaSize, anOffset - inSectionOffset);
	mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
					aTableSection.address(), aTableSection.size());

	return anOffset;
}

void
DbModifier::commit()
{
    if (!mAtomicTempFile)
        return;
    try
    {
		WriteSection aHeaderSection(Allocator::standard(), size_t(HeaderSize));
		// Set aHeaderSection to the correct size.
		aHeaderSection.size(HeaderSize);

        // Start writing sections after the header
        uint32 anOffset = HeaderOffset + HeaderSize;

        // Write auth section
		aHeaderSection.put(OffsetAuthOffset, anOffset);
        anOffset = writeAuthSection(anOffset);
        // Write schema section
		aHeaderSection.put(OffsetSchemaOffset, anOffset);
        anOffset = writeSchemaSection(anOffset);

		// Write out the file header.
		aHeaderSection.put(OffsetMagic, HeaderMagic);
		aHeaderSection.put(OffsetVersion, HeaderVersion);
        mAtomicTempFile->write(AtomicFile::FromStart, HeaderOffset,
							   aHeaderSection.address(), aHeaderSection.size());

		// Write out the versionId.
		WriteSection aVersionSection(Allocator::standard(), size_t(AtomSize));
		anOffset = aVersionSection.put(0, mVersionId);
		aVersionSection.size(anOffset);

        mAtomicTempFile->write(AtomicFile::FromEnd, 0,
							   aVersionSection.address(), aVersionSection.size());

		mAtomicTempFile->commit();
		mAtomicTempFile = NULL;
	   /* Initialize the shared memory file change mechanism */
	   pthread_once(&gCommonInitMutex, initCommon);
       
	   if (gSegment != NULL)
		{
			/*
				PLEASE NOTE:
				
				The following operation is endian safe because we are not looking 
				for monotonic increase. I have tested every possible value of 
				*gSegment, and there is no value for which alternating
				big and little endian increments will produce the original value.
			*/
       
			OSAtomicIncrement32Barrier (gSegment);
		}
    }
    catch(...)
    {
		rollback();
		throw;
    }
}

void
DbModifier::rollback() throw()
{
	// This will destroy the AtomicTempFile if we have one causing it to rollback.
	mAtomicTempFile = NULL;
}

const RecordId
DbModifier::getRecord(Table::Id inTableId, const RecordId &inRecordId,
					  CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
					  CssmData *inoutData, Allocator &inAllocator)
{
    if (mAtomicTempFile)
	{
		// We are in the midst of changing the database.
		return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
			inoutData, inAllocator);
	}
	else
	{
		return getDbVersion(false)->getRecord(inTableId, inRecordId,
			inoutAttributes, inoutData, inAllocator);
	}
}

Cursor *
DbModifier::createCursor(const CSSM_QUERY *inQuery)
{
	if (mAtomicTempFile)
	{
		// We are modifying this database.
		
		// If we have a mDbVersion already then it's a snapshot of the database
		// right before the modifications started.  So return a cursor using
		// that.
		if (mDbVersion)
			return mDbVersion->createCursor(inQuery);

		// This is a newly created but never commited database.  Return a
		// Cursor that will not return any matches.
		return new Cursor();
	}

	// Get the latest and greatest version of the db and create the cursor
	// on that.
	return getDbVersion(false)->createCursor(inQuery);
}

// Insert schema records for a new table into the metatables of the database. This gets
// called while a database is being created.

void
DbModifier::insertTableSchema(const CssmDbRecordAttributeInfo &inInfo,
	const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */)
{
	ModifiedTable &aTable = findTable(inInfo.DataRecordType);
	const MetaRecord &aMetaRecord = aTable.getMetaRecord();

	CssmAutoDbRecordAttributeData aRecordBuilder(5); // Set capacity to 5 so we don't need to grow

	// Create the entry for the SchemaRelations table.
	aRecordBuilder.add(RelationID, inInfo.recordType());
	aRecordBuilder.add(RelationName, mDb.recordName(inInfo.recordType()));

	// Insert the record into the SchemaRelations ModifiedTable
    findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
		&aRecordBuilder, NULL);
	
	ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
    for (uint32 anIndex = 0; anIndex < inInfo.size(); anIndex++)
    {
		// Create an entry for the SchemaAttributes table.
		aRecordBuilder.clear();
		aRecordBuilder.add(RelationID, inInfo.recordType());
		aRecordBuilder.add(AttributeNameFormat, inInfo.at(anIndex).nameFormat());

		uint32 attributeId = aMetaRecord.metaAttribute(inInfo.at(anIndex)).attributeId();
		
        switch (inInfo.at(anIndex).nameFormat())
        {
            case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
				aRecordBuilder.add(AttributeName, inInfo.at(anIndex).Label.AttributeName);
				break;
            case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
				aRecordBuilder.add(AttributeNameID, inInfo.at(anIndex).Label.AttributeOID);
				break;
            case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
                break;
            default:
                CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
		}

		aRecordBuilder.add(AttributeID, attributeId);
		aRecordBuilder.add(AttributeFormat, inInfo.at(anIndex).format());

		// Insert the record into the SchemaAttributes ModifiedTable
		anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
    }

	if (inIndexInfo != NULL) {
	
		if (inIndexInfo->DataRecordType != inInfo.DataRecordType &&
			inIndexInfo->NumberOfIndexes > 0)
			CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
	
		ModifiedTable &indexMetaTable = findTable(mDb.schemaIndexes.DataRecordType);
		uint32 aNumberOfIndexes = inIndexInfo->NumberOfIndexes;
		
		for (uint32 anIndex = 0; anIndex < aNumberOfIndexes; anIndex++)
		{
			const CssmDbIndexInfo &thisIndex = CssmDbIndexInfo::overlay(inIndexInfo->IndexInfo[anIndex]);
			
			// make sure the index is supported
			if (thisIndex.dataLocation() != CSSM_DB_INDEX_ON_ATTRIBUTE)
				CssmError::throwMe(CSSMERR_DL_INVALID_INDEX_INFO);
			
			// assign an index ID: the unique index is ID 0, all others are ID > 0
			uint32 indexId;
			if (thisIndex.IndexType == CSSM_DB_INDEX_UNIQUE)
				indexId = 0;
			else
				indexId = anIndex + 1;
				
			// figure out the attribute ID
			uint32 attributeId =
				aMetaRecord.metaAttribute(thisIndex.Info).attributeId();
			
			// Create an entry for the SchemaIndexes table.
			aRecordBuilder.clear();
			aRecordBuilder.add(RelationID, inInfo.DataRecordType);
			aRecordBuilder.add(IndexID, indexId);
			aRecordBuilder.add(AttributeID, attributeId);
			aRecordBuilder.add(IndexType, thisIndex.IndexType);
			aRecordBuilder.add(IndexedDataLocation, thisIndex.IndexedDataLocation);

			// Insert the record into the SchemaIndexes ModifiedTable
			indexMetaTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
			
			// update the table's index objects
			DbMutableIndex &index = aTable.findIndex(indexId, aMetaRecord, indexId == 0);
			index.appendAttribute(attributeId);
		}
	}
}

// Insert a new table. The attribute info is required; the index and parsing module
// descriptions are optional. This version gets called during the creation of a
// database.

void
DbModifier::insertTable(const CssmDbRecordAttributeInfo &inInfo,
						const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */,
						const CSSM_DB_PARSING_MODULE_INFO *inParsingModule /* = NULL */)
{
	modifyDatabase();
	createTable(new MetaRecord(inInfo));
	insertTableSchema(inInfo, inIndexInfo);
}

// Insert a new table. This is the version that gets called when a table is added
// after a database has been created.

void
DbModifier::insertTable(Table::Id inTableId, const string &inTableName,
						uint32 inNumberOfAttributes,
						const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
						uint32 inNumberOfIndexes,
						const CSSM_DB_SCHEMA_INDEX_INFO *inIndexInfo)
{
	modifyDatabase();
	ModifiedTable *aTable = createTable(new MetaRecord(inTableId, inNumberOfAttributes, inAttributeInfo));

	CssmAutoDbRecordAttributeData aRecordBuilder(6); // Set capacity to 6 so we don't need to grow

	// Create the entry for the SchemaRelations table.
	aRecordBuilder.add(RelationID, inTableId);
	aRecordBuilder.add(RelationName, inTableName);

	// Insert the record into the SchemaRelations ModifiedTable
    findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
		&aRecordBuilder, NULL);

	ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
    for (uint32 anIndex = 0; anIndex < inNumberOfAttributes; anIndex++)
    {
		// Create an entry for the SchemaAttributes table.
		aRecordBuilder.clear();
		aRecordBuilder.add(RelationID, inTableId);
		// XXX What should this be?  We set it to CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER for now
		// since the AttributeID is always valid.
		aRecordBuilder.add(AttributeNameFormat, uint32(CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER));
		aRecordBuilder.add(AttributeID, inAttributeInfo[anIndex].AttributeId);
		if (inAttributeInfo[anIndex].AttributeName)
			aRecordBuilder.add(AttributeName, inAttributeInfo[anIndex].AttributeName);
		if (inAttributeInfo[anIndex].AttributeNameID.Length > 0)
			aRecordBuilder.add(AttributeNameID, inAttributeInfo[anIndex].AttributeNameID);
		aRecordBuilder.add(AttributeFormat, inAttributeInfo[anIndex].DataType);

		// Insert the record into the SchemaAttributes ModifiedTable
		anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
    }

	ModifiedTable &anIndexTable = findTable(mDb.schemaIndexes.DataRecordType);
    for (uint32 anIndex = 0; anIndex < inNumberOfIndexes; anIndex++)
    {
		// Create an entry for the SchemaIndexes table.
		aRecordBuilder.clear();
		aRecordBuilder.add(RelationID, inTableId);
		aRecordBuilder.add(IndexID, inIndexInfo[anIndex].IndexId);
		aRecordBuilder.add(AttributeID, inIndexInfo[anIndex].AttributeId);
		aRecordBuilder.add(IndexType, inIndexInfo[anIndex].IndexType);
		aRecordBuilder.add(IndexedDataLocation, inIndexInfo[anIndex].IndexedDataLocation);

		// Insert the record into the SchemaIndexes ModifiedTable
		anIndexTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
		
		// update the table's index objects
		DbMutableIndex &index = aTable->findIndex(inIndexInfo[anIndex].IndexId,
			aTable->getMetaRecord(), inIndexInfo[anIndex].IndexType == CSSM_DB_INDEX_UNIQUE);
		index.appendAttribute(inIndexInfo[anIndex].AttributeId);
    }
}



bool DbModifier::hasTable(Table::Id inTableId)
{
	return getDbVersion(false)->hasTable(inTableId);
}



ModifiedTable &
DbModifier::findTable(Table::Id inTableId)
{
    ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
    if (it == mModifiedTableMap.end())
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
	return *it->second;
}


//
// AppleDatabaseManager implementation
//

AppleDatabaseManager::AppleDatabaseManager(const AppleDatabaseTableName *tableNames)
	:	DatabaseManager(),
		mTableNames(tableNames)
{
	// make sure that a proper set of table ids and names has been provided
	
	if (!mTableNames)
		CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
	else {
		uint32 i;
		for (i = 0; mTableNames[i].mTableName; i++) {}
		if (i < AppleDatabaseTableName::kNumRequiredTableNames)
			CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
	}
}

Database *
AppleDatabaseManager::make(const DbName &inDbName)
{
    return new AppleDatabase(inDbName, mTableNames);
}


//
// AppleDbContext implementation
//

/* This is the version 0 CSSM_APPLEDL_OPEN_PARAMETERS struct used up to 10.2.x. */
extern "C" {

typedef struct cssm_appledl_open_parameters_v0
{
		uint32 length;  /* Should be sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0). */
		uint32 version; /* Should be 0. */
		CSSM_BOOL autoCommit;
} CSSM_APPLEDL_OPEN_PARAMETERS_V0;

};

AppleDbContext::AppleDbContext(Database &inDatabase,
							   DatabaseSession &inDatabaseSession,
							   CSSM_DB_ACCESS_TYPE inAccessRequest,
							   const AccessCredentials *inAccessCred,
							   const void *inOpenParameters) :
	DbContext(inDatabase, inDatabaseSession, inAccessRequest, inAccessCred),
	mAutoCommit(true),
	mMode(0666)
{
	const CSSM_APPLEDL_OPEN_PARAMETERS *anOpenParameters =
		reinterpret_cast<const CSSM_APPLEDL_OPEN_PARAMETERS *>(inOpenParameters);

	if (anOpenParameters)
	{
		switch (anOpenParameters->version)
		{
		case 1:
			if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS))
				CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);

			if (anOpenParameters->mask & kCSSM_APPLEDL_MASK_MODE)
				mMode = anOpenParameters->mode;
			/*DROPTHROUGH*/
		case 0:
			if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0))
				CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);

			mAutoCommit = anOpenParameters->autoCommit == CSSM_FALSE ? false : true;
			break;

		default:
			CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
		}
	}
}

AppleDbContext::~AppleDbContext()
{
}

//
// AppleDatabase implementation
//
AppleDatabase::AppleDatabase(const DbName &inDbName, const AppleDatabaseTableName *tableNames) :
    Database(inDbName),
	schemaRelations(tableNames[AppleDatabaseTableName::kSchemaInfo].mTableId,
		sizeof(AttrSchemaRelations) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
		const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaRelations)),
	schemaAttributes(tableNames[AppleDatabaseTableName::kSchemaAttributes].mTableId,
		sizeof(AttrSchemaAttributes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
		const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaAttributes)),
	schemaIndexes(tableNames[AppleDatabaseTableName::kSchemaIndexes].mTableId,
		sizeof(AttrSchemaIndexes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
		const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaIndexes)),
	schemaParsingModule(tableNames[AppleDatabaseTableName::kSchemaParsingModule].mTableId,
		sizeof(AttrSchemaParsingModule) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
		const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaParsingModule)),
    mAtomicFile(mDbName.dbName()),
	mDbModifier(mAtomicFile, *this),
	mTableNames(tableNames)
{
	/* temp check for X509Anchors access - this should removed before Leopard GM */
	if(!strcmp(inDbName.dbName(), "/System/Library/Keychains/X509Anchors")) {
		Syslog::alert("Warning: accessing obsolete X509Anchors.");
	}
}

AppleDatabase::~AppleDatabase()
{
}

// Return the name of a record type. This uses a table that maps record types
// to record names. The table is provided when the database is created.

const char *AppleDatabase::recordName(CSSM_DB_RECORDTYPE inRecordType) const
{
	if (inRecordType == CSSM_DL_DB_RECORD_ANY || inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
		
	for (uint32 i = 0; mTableNames[i].mTableName; i++)
		if (mTableNames[i].mTableId == inRecordType)
			return mTableNames[i].mTableName;
			
	return "";
}

DbContext *
AppleDatabase::makeDbContext(DatabaseSession &inDatabaseSession,
                             CSSM_DB_ACCESS_TYPE inAccessRequest,
                             const AccessCredentials *inAccessCred,
                             const void *inOpenParameters)
{
    return new AppleDbContext(*this, inDatabaseSession, inAccessRequest,
							  inAccessCred, inOpenParameters);
}

void
AppleDatabase::dbCreate(DbContext &inDbContext, const CSSM_DBINFO &inDBInfo,
                        const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry)
{
	AppleDbContext &context = safer_cast<AppleDbContext &>(inDbContext);
    try
    {
		StLock<Mutex> _(mWriteLock);
        mDbModifier.createDatabase(inDBInfo, inInitialAclEntry, context.mode());
    }
    catch(...)
    {
        mDbModifier.rollback();
        throw;
    }
	if (context.autoCommit())
		mDbModifier.commit();
}

void
AppleDatabase::dbOpen(DbContext &inDbContext)
{
	mDbModifier.openDatabase();
}

void
AppleDatabase::dbClose()
{
	StLock<Mutex> _(mWriteLock);
	mDbModifier.closeDatabase();
}

void
AppleDatabase::dbDelete(DatabaseSession &inDatabaseSession,
                        const AccessCredentials *inAccessCred)
{
	StLock<Mutex> _(mWriteLock);
    // XXX Check callers credentials.
	mDbModifier.deleteDatabase();
}

void
AppleDatabase::createRelation(DbContext &inDbContext,
                              CSSM_DB_RECORDTYPE inRelationID,
                              const char *inRelationName,
                              uint32 inNumberOfAttributes,
                              const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
                              uint32 inNumberOfIndexes,
                              const CSSM_DB_SCHEMA_INDEX_INFO &inIndexInfo)
{
	try
	{
		StLock<Mutex> _(mWriteLock);
		// XXX Fix the refs here.
		mDbModifier.insertTable(inRelationID, inRelationName,
								inNumberOfAttributes, inAttributeInfo,
								inNumberOfIndexes, &inIndexInfo);
	}
	catch(...)
	{
		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.rollback();
		throw;
	}
	if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
		mDbModifier.commit();
}

void
AppleDatabase::destroyRelation(DbContext &inDbContext,
                               CSSM_DB_RECORDTYPE inRelationID)
{
	try
	{
		StLock<Mutex> _(mWriteLock);
		mDbModifier.deleteTable(inRelationID);
	}
	catch(...)
	{
		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.rollback();
		throw;
	}
	if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
		mDbModifier.commit();
}

void
AppleDatabase::authenticate(DbContext &inDbContext,
                            CSSM_DB_ACCESS_TYPE inAccessRequest,
                            const AccessCredentials &inAccessCred)
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

void
AppleDatabase::getDbAcl(DbContext &inDbContext,
                        const CSSM_STRING *inSelectionTag,
                        uint32 &outNumberOfAclInfos,
                        CSSM_ACL_ENTRY_INFO_PTR &outAclInfos)
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

void
AppleDatabase::changeDbAcl(DbContext &inDbContext,
                           const AccessCredentials &inAccessCred,
                           const CSSM_ACL_EDIT &inAclEdit)
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

void
AppleDatabase::getDbOwner(DbContext &inDbContext,
						  CSSM_ACL_OWNER_PROTOTYPE &outOwner)
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

void
AppleDatabase::changeDbOwner(DbContext &inDbContext,
                             const AccessCredentials &inAccessCred,
                             const CSSM_ACL_OWNER_PROTOTYPE &inNewOwner)
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

char *
AppleDatabase::getDbNameFromHandle(const DbContext &inDbContext) const
{
    CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}

CSSM_DB_UNIQUE_RECORD_PTR
AppleDatabase::dataInsert(DbContext &inDbContext,
                          CSSM_DB_RECORDTYPE inRecordType,
                          const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
                          const CssmData *inData)
{
	CSSM_DB_UNIQUE_RECORD_PTR anUniqueRecordPtr = NULL;
	try
	{
		StLock<Mutex> _(mWriteLock);
		const RecordId aRecordId =
			mDbModifier.insertRecord(inRecordType, inAttributes, inData);

		anUniqueRecordPtr = createUniqueRecord(inDbContext, inRecordType,
											   aRecordId);
		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.commit();
	}
	catch(...)
	{
		if (anUniqueRecordPtr != NULL)
			freeUniqueRecord(inDbContext, *anUniqueRecordPtr);

		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.rollback();
		throw;
	}

	return anUniqueRecordPtr;
}

void
AppleDatabase::dataDelete(DbContext &inDbContext,
                          const CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
{
    try
    {
		// syslog if it's the .Mac password
		CSSM_DB_RECORD_ATTRIBUTE_DATA attrData;
		// we have to do this in two phases -- the first to get the record type, and the second to actually read the attributes.  Otherwise, we might get
		// an exception.
		memset(&attrData, 0, sizeof(attrData));
		dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
		
		if (attrData.DataRecordType == CSSM_DL_DB_RECORD_GENERIC_PASSWORD)
		{
			CSSM_DB_ATTRIBUTE_DATA attributes;
			
			// setup some attributes and see if we are indeed the .Mac password
			attributes.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
			attributes.Info.Label.AttributeID = 'svce';
			attributes.Info.AttributeFormat = 0;
			attributes.NumberOfValues = 1;
			attributes.Value = NULL;

			attrData.NumberOfAttributes = 1;
			attrData.AttributeData = &attributes;		
			
			dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
			
			// now check the results
			std::string dataString((const char*) attrData.AttributeData[0].Value[0].Data, attrData.AttributeData[0].Value[0].Length);
			if (dataString == "iTools")
			{
				syslog(LOG_WARNING, "Warning: Removed .Me password");
			}
			
			free(attrData.AttributeData[0].Value[0].Data);
			free(attrData.AttributeData[0].Value);
		}
		
		StLock<Mutex> _(mWriteLock);
		Table::Id aTableId;
		const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
		mDbModifier.deleteRecord(aTableId, aRecordId);
	}
    catch(...)
    {
		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.rollback();
        throw;
    }

	if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
		mDbModifier.commit();
}

void
AppleDatabase::dataModify(DbContext &inDbContext,
                          CSSM_DB_RECORDTYPE inRecordType,
                          CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord,
                          const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributesToBeModified,
                          const CssmData *inDataToBeModified,
                          CSSM_DB_MODIFY_MODE inModifyMode)
{
    try
    {
		StLock<Mutex> _(mWriteLock);
		Table::Id aTableId;
		const RecordId oldRecordId = parseUniqueRecord(inoutUniqueRecord,
			aTableId);
#if 1
		if (inRecordType != aTableId)
#else
		if (inRecordType != aTableId &&
			inRecordType != CSSM_DL_DB_RECORD_ANY &&
			!(inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS &&
			  (aTableId == CSSM_DL_DB_RECORD_PUBLIC_KEY ||
			   aTableId == CSSM_DL_DB_RECORD_PRIVATE_KEY ||
			   aTableId == CSSM_DL_DB_RECORD_SYMMETRIC_KEY)))
#endif
		{
			CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
		}

		const RecordId newRecordId =
			mDbModifier.updateRecord(aTableId,
									 oldRecordId,
									 inAttributesToBeModified,
									 inDataToBeModified,
									 inModifyMode);
		updateUniqueRecord(inDbContext, aTableId, newRecordId,
			inoutUniqueRecord);
	}
    catch(...)
    {
		if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
			mDbModifier.rollback();
        throw;
    }

	if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
		mDbModifier.commit();
}

CSSM_HANDLE
AppleDatabase::dataGetFirst(DbContext &inDbContext,
                            const CssmQuery *inQuery,
                            CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
                            CssmData *inoutData,
                            CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
{
	// XXX: register Cursor with DbContext and have DbContext call
	// dataAbortQuery for all outstanding Query objects on close.
	auto_ptr<Cursor> aCursor(mDbModifier.createCursor(inQuery));
	Table::Id aTableId;
	RecordId aRecordId;
	
	if (!aCursor->next(aTableId, inoutAttributes, inoutData,
					   inDbContext.mDatabaseSession, aRecordId))
		// return a NULL handle, and implicitly delete the cursor
		return CSSM_INVALID_HANDLE;

	outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);
	return aCursor.release()->handle(); // We didn't throw so keep the Cursor around.
}

bool
AppleDatabase::dataGetNext(DbContext &inDbContext,
                           CSSM_HANDLE inResultsHandle,
                           CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
                           CssmData *inoutData,
                           CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
{
	auto_ptr<Cursor> aCursor(&HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE));
	Table::Id aTableId;
	RecordId aRecordId;
	
	if (!aCursor->next(aTableId, inoutAttributes, inoutData, inDbContext.mDatabaseSession, aRecordId))
		return false;

	outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);

	aCursor.release();
	return true;
}

void
AppleDatabase::dataAbortQuery(DbContext &inDbContext,
                              CSSM_HANDLE inResultsHandle)
{
	delete &HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE);
}

void
AppleDatabase::dataGetFromUniqueRecordId(DbContext &inDbContext,
                                         const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
                                         CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
                                         CssmData *inoutData)
{
	Table::Id aTableId;
	const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
	// XXX Change CDSA spec to use new RecordId returned by this function
	mDbModifier.getRecord(aTableId, aRecordId, inoutAttributes, inoutData,
						  inDbContext.mDatabaseSession);
}

void
AppleDatabase::freeUniqueRecord(DbContext &inDbContext,
                                CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
{
	if (inUniqueRecord.RecordIdentifier.Length != 0
		&& inUniqueRecord.RecordIdentifier.Data != NULL)
	{
		inUniqueRecord.RecordIdentifier.Length = 0;
		inDbContext.mDatabaseSession.free(inUniqueRecord.RecordIdentifier.Data);
	}
	inDbContext.mDatabaseSession.free(&inUniqueRecord);
}

void
AppleDatabase::updateUniqueRecord(DbContext &inDbContext,
								  CSSM_DB_RECORDTYPE inTableId,
								  const RecordId &inRecordId,
								  CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord)
{
	uint32 *aBuffer = reinterpret_cast<uint32 *>(inoutUniqueRecord.RecordIdentifier.Data);
	aBuffer[0] = inTableId;
	aBuffer[1] = inRecordId.mRecordNumber;
	aBuffer[2] = inRecordId.mCreateVersion;
	aBuffer[3] = inRecordId.mRecordVersion;
}

CSSM_DB_UNIQUE_RECORD_PTR
AppleDatabase::createUniqueRecord(DbContext &inDbContext,
								  CSSM_DB_RECORDTYPE inTableId,
								  const RecordId &inRecordId)
{
	CSSM_DB_UNIQUE_RECORD_PTR aUniqueRecord =
		inDbContext.mDatabaseSession.alloc<CSSM_DB_UNIQUE_RECORD>();
	memset(aUniqueRecord, 0, sizeof(*aUniqueRecord));
	aUniqueRecord->RecordIdentifier.Length = sizeof(uint32) * 4;
	try
	{
		aUniqueRecord->RecordIdentifier.Data =
			inDbContext.mDatabaseSession.alloc<uint8>(sizeof(uint32) * 4);
		updateUniqueRecord(inDbContext, inTableId, inRecordId, *aUniqueRecord);
	}
	catch(...)
	{
		inDbContext.mDatabaseSession.free(aUniqueRecord);
		throw;
	}

	return aUniqueRecord;
}

const RecordId
AppleDatabase::parseUniqueRecord(const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
								 CSSM_DB_RECORDTYPE &outTableId)
{
	if (inUniqueRecord.RecordIdentifier.Length != sizeof(uint32) * 4)
		CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);

	uint32 *aBuffer = reinterpret_cast<uint32 *>(inUniqueRecord.RecordIdentifier.Data);
	outTableId = aBuffer[0];
	return RecordId(aBuffer[1], aBuffer[2], aBuffer[3]);
}

void
AppleDatabase::passThrough(DbContext &dbContext,
						   uint32 passThroughId,
						   const void *inputParams,
						   void **outputParams)
{
	switch (passThroughId)
	{
	case CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT:
		{
			AppleDbContext &dbc = safer_cast<AppleDbContext &>(dbContext);
			// Return the old state of the autoCommit flag if requested
			if (outputParams)
				*reinterpret_cast<CSSM_BOOL *>(outputParams) = dbc.autoCommit();
			dbc.autoCommit(inputParams ? CSSM_TRUE : CSSM_FALSE);
		}
		break;
		
	case CSSM_APPLEFILEDL_COMMIT:
		mDbModifier.commit();
		break;
		
	case CSSM_APPLEFILEDL_ROLLBACK:
		mDbModifier.rollback();
		break;

	case CSSM_APPLECSPDL_DB_RELATION_EXISTS:
	{
		CSSM_BOOL returnValue;
		
		CSSM_DB_RECORDTYPE recordType = *(CSSM_DB_RECORDTYPE*) inputParams;
		if (recordType == CSSM_DL_DB_RECORD_ANY || recordType == CSSM_DL_DB_RECORD_ALL_KEYS)
		{
			returnValue = CSSM_TRUE;
		}
		else
		{
			returnValue = mDbModifier.hasTable(recordType);
		}
		
		*(CSSM_BOOL*) outputParams = returnValue;
		break;
	}

	default:
		CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
		break;
	}
}