]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_filedb/lib/AppleDatabase.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_filedb / lib / AppleDatabase.cpp
diff --git a/libsecurity_filedb/lib/AppleDatabase.cpp b/libsecurity_filedb/lib/AppleDatabase.cpp
new file mode 100644 (file)
index 0000000..dbe9ece
--- /dev/null
@@ -0,0 +1,2561 @@
+/*
+ * 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;
+       }
+}