--- /dev/null
+/*
+ * 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;
+ }
+}