2 * Copyright (c) 2000-2001,2003,2011-2014 Apple Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 // AppleDatabase.cpp - Description t.b.d.
22 #include "AppleDatabase.h"
23 #include <security_cdsa_plugin/DatabaseSession.h>
24 #include <security_cdsa_plugin/DbContext.h>
25 #include <security_cdsa_utilities/cssmdb.h>
26 #include <Security/cssmapple.h>
27 #include <security_utilities/trackingallocator.h>
28 #include <security_utilities/logging.h>
31 #include <libkern/OSAtomic.h>
35 #include <Security/cssmapplePriv.h>
39 static const char *kAppleDatabaseChanged
= "com.apple.AppleDatabaseChanged";
41 /* Number of seconds after which we open/pread/close a db to check it's
42 version number even if we didn't get any notifications. Note that we always
43 check just after we take a write lock and whenever we get a notification
44 that any db on the system has changed. */
45 static const CFTimeInterval kForceReReadTime
= 15.0;
47 /* Token on which we receive notifications and the pthread_once_t protecting
48 it's initialization. */
49 pthread_once_t gCommonInitMutex
= PTHREAD_ONCE_INIT
;
51 /* Global counter of how many notifications we have received and a lock to
52 protect the counter. */
53 static int kSegmentSize
= 4;
54 int32_t* gSegment
= NULL
;
56 /* Registration routine for notifcations. Called inside a pthread_once(). */
57 static void initCommon(void)
60 int segmentDescriptor
= shm_open (kAppleDatabaseChanged
, O_RDWR
| O_CREAT
, S_IRWXU
| S_IRWXG
| S_IRWXO
);
61 if (segmentDescriptor
< 0)
66 // set the segment size
67 ftruncate (segmentDescriptor
, kSegmentSize
);
70 int32_t* tmp
= (int32_t*) mmap (NULL
, kSegmentSize
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, segmentDescriptor
, 0);
71 close (segmentDescriptor
);
73 if (tmp
== (int32_t*) -1) // can't map the memory?
86 Table::Table(const ReadSection
&inTableSection
) :
87 mMetaRecord(inTableSection
[OffsetId
]),
88 mTableSection(inTableSection
),
89 mRecordsCount(inTableSection
[OffsetRecordsCount
]),
90 mFreeListHead(inTableSection
[OffsetFreeListHead
]),
91 mRecordNumbersCount(inTableSection
[OffsetRecordNumbersCount
])
93 // can't easily initialize indexes here, since meta record is incomplete
94 // until much later... see DbVersion::open()
99 for_each_map_delete(mIndexMap
.begin(), mIndexMap
.end());
103 Table::readIndexSection()
105 uint32 indexSectionOffset
= mTableSection
.at(OffsetIndexesOffset
);
107 uint32 numIndexes
= mTableSection
.at(indexSectionOffset
+ AtomSize
);
109 for (uint32 i
= 0; i
< numIndexes
; i
++) {
110 uint32 indexOffset
= mTableSection
.at(indexSectionOffset
+ (i
+ 2) * AtomSize
);
111 ReadSection
indexSection(mTableSection
.subsection(indexOffset
));
113 auto_ptr
<DbConstIndex
> index(new DbConstIndex(*this, indexSection
));
114 mIndexMap
.insert(ConstIndexMap::value_type(index
->indexId(), index
.get()));
120 Table::createCursor(const CSSM_QUERY
*inQuery
, const DbVersion
&inDbVersion
) const
122 // if an index matches the query, return a cursor which uses the index
124 ConstIndexMap::const_iterator it
;
125 DbQueryKey
*queryKey
;
127 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
128 if (it
->second
->matchesQuery(*inQuery
, queryKey
)) {
129 IndexCursor
*cursor
= new IndexCursor(queryKey
, inDbVersion
, *this, it
->second
);
133 // otherwise, return a cursor that iterates over all table records
135 return new LinearCursor(inQuery
, inDbVersion
, *this);
139 Table::getRecordSection(uint32 inRecordNumber
) const
141 if (inRecordNumber
>= mRecordNumbersCount
)
142 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID
);
144 uint32 aRecordOffset
= mTableSection
[OffsetRecordNumbers
+ AtomSize
147 // Check if this RecordNumber has been deleted.
148 if (aRecordOffset
& 1 || aRecordOffset
== 0)
149 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
151 return MetaRecord::readSection(mTableSection
, aRecordOffset
);
155 Table::getRecord(const RecordId
&inRecordId
,
156 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
158 Allocator
&inAllocator
) const
160 const ReadSection aRecordSection
= getRecordSection(inRecordId
.mRecordNumber
);
161 const RecordId aRecordId
= MetaRecord::unpackRecordId(aRecordSection
);
163 // Make sure the RecordNumber matches that in the RecordId we just retrived.
164 if (aRecordId
.mRecordNumber
!= inRecordId
.mRecordNumber
)
165 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
167 if (aRecordId
.mCreateVersion
!= inRecordId
.mCreateVersion
)
168 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID
);
170 // XXX Figure out which value to pass for inQueryFlags (5th) argument
171 mMetaRecord
.unpackRecord(aRecordSection
, inAllocator
, inoutAttributes
,
177 Table::popFreeList(uint32
&aFreeListHead
) const
179 assert(aFreeListHead
| 1);
180 uint32 anOffset
= aFreeListHead
^ 1;
181 uint32 aRecordNumber
= (anOffset
- OffsetRecordNumbers
) / AtomSize
;
182 aFreeListHead
= mTableSection
[anOffset
];
183 return aRecordNumber
;
187 Table::getRecordsSection() const
189 return mTableSection
.subsection(mTableSection
[OffsetRecords
]);
193 Table::matchesTableId(Id inTableId
) const
195 Id anId
= mMetaRecord
.dataRecordType();
196 if (inTableId
== CSSM_DL_DB_RECORD_ANY
) // All non schema tables.
197 return !(CSSM_DB_RECORDTYPE_SCHEMA_START
<= anId
198 && anId
< CSSM_DB_RECORDTYPE_SCHEMA_END
);
200 if (inTableId
== CSSM_DL_DB_RECORD_ALL_KEYS
) // All key tables.
201 return (anId
== CSSM_DL_DB_RECORD_PUBLIC_KEY
202 || anId
== CSSM_DL_DB_RECORD_PRIVATE_KEY
203 || anId
== CSSM_DL_DB_RECORD_SYMMETRIC_KEY
);
205 return inTableId
== anId
; // Only if exact match.
212 ModifiedTable::ModifiedTable(const Table
*inTable
) :
215 mRecordNumberCount(inTable
->recordNumberCount()),
216 mFreeListHead(inTable
->freeListHead()),
221 ModifiedTable::ModifiedTable(MetaRecord
*inMetaRecord
) :
223 mNewMetaRecord(inMetaRecord
),
224 mRecordNumberCount(0),
230 ModifiedTable::~ModifiedTable()
232 for_each_map_delete(mIndexMap
.begin(), mIndexMap
.end());
233 for_each_map_delete(mInsertedMap
.begin(), mInsertedMap
.end());
235 delete mNewMetaRecord
;
239 ModifiedTable::deleteRecord(const RecordId
&inRecordId
)
243 uint32 aRecordNumber
= inRecordId
.mRecordNumber
;
245 // remove the record from all the indexes
246 MutableIndexMap::iterator it
;
247 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
248 it
->second
->removeRecord(aRecordNumber
);
250 InsertedMap::iterator anIt
= mInsertedMap
.find(aRecordNumber
);
251 if (anIt
== mInsertedMap
.end())
253 // If we have no old table than this record can not exist yet.
255 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
257 #if RECORDVERSIONCHECK
258 const RecordId aRecordId
= MetaRecord::unpackRecordId(mTable
->getRecordSection(aRecordNumber
));
259 if (aRecordId
.mRecordVersion
!= inRecordId
.mRecordVersion
)
260 CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED
);
263 // Schedule the record for deletion
264 if (!mDeletedSet
.insert(aRecordNumber
).second
)
265 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
); // It was already deleted
269 const RecordId aRecordId
= MetaRecord::unpackRecordId(*anIt
->second
);
270 if (aRecordId
.mCreateVersion
!= inRecordId
.mCreateVersion
)
271 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
273 #if RECORDVERSIONCHECK
274 if (aRecordId
.mRecordVersion
!= inRecordId
.mRecordVersion
)
275 CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED
);
278 // Remove the inserted (but uncommited) record. It should already be in mDeletedSet
279 // if it existed previously in mTable.
281 mInsertedMap
.erase(anIt
);
286 ModifiedTable::insertRecord(uint32 inVersionId
,
287 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributes
,
288 const CssmData
*inData
)
292 auto_ptr
<WriteSection
> aWriteSection(new WriteSection());
293 getMetaRecord().packRecord(*aWriteSection
, inAttributes
, inData
);
294 uint32 aRecordNumber
= nextRecordNumber();
296 // add the record to all the indexes; this will throw if the new record
297 // violates a unique index
298 MutableIndexMap::iterator it
;
299 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
300 it
->second
->insertRecord(aRecordNumber
, *(aWriteSection
.get()));
302 // schedule the record for insertion
303 RecordId
aRecordId(aRecordNumber
, inVersionId
);
304 MetaRecord::packRecordId(aRecordId
, *aWriteSection
);
305 mInsertedMap
.insert(InsertedMap::value_type(aRecordNumber
, aWriteSection
.get()));
307 aWriteSection
.release();
313 ModifiedTable::updateRecord(const RecordId
&inRecordId
,
314 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributes
,
315 const CssmData
*inData
,
316 CSSM_DB_MODIFY_MODE inModifyMode
)
320 uint32 aRecordNumber
= inRecordId
.mRecordNumber
;
321 InsertedMap::iterator anIt
= mInsertedMap
.find(aRecordNumber
);
323 // aReUpdate is true iff we are updating an already updated record.
324 bool aReUpdate
= anIt
!= mInsertedMap
.end();
326 // If we are not re-updating and there is no old table than this record does not exist yet.
327 if (!aReUpdate
&& !mTable
)
328 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
330 const ReadSection
&anOldDbRecord
= aReUpdate
? *anIt
->second
: mTable
->getRecordSection(aRecordNumber
);
331 const RecordId aRecordId
= MetaRecord::unpackRecordId(anOldDbRecord
);
333 // Did someone else delete the record we are trying to update.
334 if (aRecordId
.mCreateVersion
!= inRecordId
.mCreateVersion
)
335 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
337 #if RECORDVERSIONCHECK
338 // Is the record we that our update is based on current?
339 if (aRecordId
.mRecordVersion
!= inRecordId
.mRecordVersion
)
340 CssmError::throwMe(CSSMERR_DL_STALE_UNIQUE_RECORD
);
343 // Update the actual packed record.
344 auto_ptr
<WriteSection
> aDbRecord(new WriteSection());
345 getMetaRecord().updateRecord(anOldDbRecord
, *aDbRecord
,
346 CssmDbRecordAttributeData::overlay(inAttributes
), inData
, inModifyMode
);
349 // Bump the RecordVersion of this record.
350 RecordId
aNewRecordId(aRecordNumber
, inRecordId
.mCreateVersion
, inRecordId
.mRecordVersion
+ 1);
351 // Store the RecordVersion in the packed aDbRecord.
352 MetaRecord::packRecordId(aNewRecordId
, *aDbRecord
);
354 if (!aReUpdate
&& !mDeletedSet
.insert(aRecordNumber
).second
)
355 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
); // Record was already in mDeletedSet
357 // remove the original record from all the indexes
358 MutableIndexMap::iterator it
;
359 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
360 it
->second
->removeRecord(aRecordNumber
);
364 // Add the updated record to all the indexes; this will throw if the new record
365 // violates a unique index
366 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
367 it
->second
->insertRecord(aRecordNumber
, *(aDbRecord
.get()));
371 // Get rid of anOldDbRecord from the inserted map and replace it
374 anIt
->second
= aDbRecord
.get();
378 // First time though so let's just put the new value in the map.
379 mInsertedMap
.insert(InsertedMap::value_type(aRecordNumber
, aDbRecord
.get()));
385 // We only remove aRecordNumber from mDeletedSet if we added it above.
387 mDeletedSet
.erase(aRecordNumber
);
389 // The 2 operations below are an attempt to preserve the indices when
392 // Remove the updated record from all the indexes
393 MutableIndexMap::iterator it
;
394 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
395 it
->second
->removeRecord(aRecordNumber
);
397 // Add the original record back to all the indexes
398 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++)
399 it
->second
->insertRecord(aRecordNumber
, anOldDbRecord
);
408 ModifiedTable::getRecord(const RecordId
&inRecordId
,
409 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
411 Allocator
&inAllocator
) const
415 uint32 aRecordNumber
= inRecordId
.mRecordNumber
;
416 InsertedMap::const_iterator anIt
= mInsertedMap
.find(aRecordNumber
);
417 if (anIt
!= mInsertedMap
.end())
419 // We found the record in mInsertedMap so we use the inserted
421 const ReadSection
&aRecordSection
= *(anIt
->second
);
422 const RecordId aRecordId
= MetaRecord::unpackRecordId(aRecordSection
);
424 // Make sure the RecordNumber matches that in the RecordId we just retrived.
425 if (aRecordId
.mRecordNumber
!= aRecordNumber
)
426 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
428 if (aRecordId
.mCreateVersion
!= inRecordId
.mCreateVersion
)
429 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID
);
431 // XXX Figure out which value to pass for inQueryFlags (5th) argument
432 getMetaRecord().unpackRecord(aRecordSection
, inAllocator
,
433 inoutAttributes
, inoutData
, 0);
437 else if (mDeletedSet
.find(aRecordNumber
) != mDeletedSet
.end())
439 // If aRecordNumber was not in mInsertedMap but it was in
440 // mDeletedSet then it was deleted but not yet commited.
441 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
446 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND
);
448 // Either this table wasn't modified yet or we didn't find aRecordNumber in
449 // mInsertedMap nor mDeletedSet so just ask mTable for it.
450 return mTable
->getRecord(inRecordId
, inoutAttributes
, inoutData
,
455 ModifiedTable::nextRecordNumber()
457 // If we still have unused free records in mTable get the next one.
459 return mTable
->popFreeList(mFreeListHead
);
461 // Bump up the mRecordNumberCount so we don't reuse the same one.
462 return mRecordNumberCount
++;
466 ModifiedTable::recordNumberCount() const
468 uint32 anOldMax
= !mTable
? 0 : mTable
->recordNumberCount() - 1;
469 uint32 anInsertedMax
= mInsertedMap
.empty() ? 0 : mInsertedMap
.rbegin()->first
;
471 DeletedSet::reverse_iterator anIt
= mDeletedSet
.rbegin();
472 DeletedSet::reverse_iterator anEnd
= mDeletedSet
.rend();
473 for (; anIt
!= anEnd
; anIt
++)
475 if (*anIt
!= anOldMax
|| anOldMax
<= anInsertedMax
)
480 return max(anOldMax
,anInsertedMax
) + 1;
484 ModifiedTable::getMetaRecord() const
486 return mNewMetaRecord
? *mNewMetaRecord
: mTable
->getMetaRecord();
489 // prepare to modify the table
492 ModifiedTable::modifyTable()
495 createMutableIndexes();
500 // create mutable indexes from the read-only indexes in the underlying table
503 ModifiedTable::createMutableIndexes()
508 Table::ConstIndexMap::const_iterator it
;
509 for (it
= mTable
->mIndexMap
.begin(); it
!= mTable
->mIndexMap
.end(); it
++) {
510 auto_ptr
<DbMutableIndex
> mutableIndex(new DbMutableIndex(*it
->second
));
511 mIndexMap
.insert(MutableIndexMap::value_type(it
->first
, mutableIndex
.get()));
512 mutableIndex
.release();
516 // find, and create if needed, an index with the given id
519 ModifiedTable::findIndex(uint32 indexId
, const MetaRecord
&metaRecord
, bool isUniqueIndex
)
521 MutableIndexMap::iterator it
= mIndexMap
.find(indexId
);
523 if (it
== mIndexMap
.end()) {
524 // create the new index
525 auto_ptr
<DbMutableIndex
> index(new DbMutableIndex(metaRecord
, indexId
, isUniqueIndex
));
526 it
= mIndexMap
.insert(MutableIndexMap::value_type(indexId
, index
.get())).first
;
534 ModifiedTable::writeIndexSection(WriteSection
&tableSection
, uint32 offset
)
536 MutableIndexMap::iterator it
;
538 tableSection
.put(Table::OffsetIndexesOffset
, offset
);
540 // leave room for the size, to be written later
541 uint32 indexSectionOffset
= offset
;
544 offset
= tableSection
.put(offset
, (uint32
)mIndexMap
.size());
546 // leave room for the array of offsets to the indexes
547 uint32 indexOffsetOffset
= offset
;
548 offset
+= mIndexMap
.size() * AtomSize
;
551 for (it
= mIndexMap
.begin(); it
!= mIndexMap
.end(); it
++) {
552 indexOffsetOffset
= tableSection
.put(indexOffsetOffset
, offset
);
553 offset
= it
->second
->writeIndex(tableSection
, offset
);
556 // write the total index section size
557 tableSection
.put(indexSectionOffset
, offset
- indexSectionOffset
);
563 ModifiedTable::writeTable(AtomicTempFile
&inAtomicTempFile
, uint32 inSectionOffset
)
565 if (mTable
&& !mIsModified
) {
566 // the table has not been modified, so we can just dump the old table
567 // section into the new database
569 const ReadSection
&tableSection
= mTable
->getTableSection();
570 uint32 tableSize
= tableSection
.at(Table::OffsetSize
);
572 inAtomicTempFile
.write(AtomicFile::FromStart
, inSectionOffset
,
573 tableSection
.range(Range(0, tableSize
)), tableSize
);
575 return inSectionOffset
+ tableSize
;
578 // We should have an old mTable or a mNewMetaRecord but not both.
579 assert(mTable
!= nil
^ mNewMetaRecord
!= nil
);
580 const MetaRecord
&aNewMetaRecord
= getMetaRecord();
582 uint32 aRecordsCount
= 0;
583 uint32 aRecordNumbersCount
= recordNumberCount();
584 uint32 aRecordsOffset
= Table::OffsetRecordNumbers
+ AtomSize
* aRecordNumbersCount
;
585 WriteSection
aTableSection(Allocator::standard(), aRecordsOffset
);
586 aTableSection
.size(aRecordsOffset
);
587 aTableSection
.put(Table::OffsetId
, aNewMetaRecord
.dataRecordType());
588 aTableSection
.put(Table::OffsetRecords
, aRecordsOffset
);
589 aTableSection
.put(Table::OffsetRecordNumbersCount
, aRecordNumbersCount
);
591 uint32 anOffset
= inSectionOffset
+ aRecordsOffset
;
595 // XXX Handle schema changes in the future.
596 assert(mNewMetaRecord
== nil
);
598 // We have a modified old table so copy all non deleted records
599 // The code below is rather elaborate, but this is because it attempts
600 // to copy large ranges of non deleted records with single calls
601 // to AtomicFile::write()
602 uint32 anOldRecordsCount
= mTable
->getRecordsCount();
603 ReadSection aRecordsSection
= mTable
->getRecordsSection();
604 uint32 aReadOffset
= 0; // Offset of current record
605 uint32 aWriteOffset
= aRecordsOffset
; // Offset for current write record
606 uint32 aBlockStart
= aReadOffset
; // Starting point for read
607 uint32 aBlockSize
= 0; // Size of block to read
608 for (uint32 aRecord
= 0; aRecord
< anOldRecordsCount
; aRecord
++)
610 ReadSection aRecordSection
= MetaRecord::readSection(aRecordsSection
, aReadOffset
);
611 uint32 aRecordNumber
= MetaRecord::unpackRecordNumber(aRecordSection
);
612 uint32 aRecordSize
= aRecordSection
.size();
613 aReadOffset
+= aRecordSize
;
614 if (mDeletedSet
.find(aRecordNumber
) == mDeletedSet
.end())
616 // This record has not been deleted. Register the offset
617 // at which it will be in the new file in aTableSection.
618 aTableSection
.put(Table::OffsetRecordNumbers
619 + AtomSize
* aRecordNumber
,
621 aWriteOffset
+= aRecordSize
;
622 aBlockSize
+= aRecordSize
;
624 // XXX update all indexes being created.
628 // The current record has been deleted. Copy all records up
629 // to but not including the current one to the new file.
632 inAtomicTempFile
.write(AtomicFile::FromStart
, anOffset
,
633 aRecordsSection
.range(Range(aBlockStart
,
636 anOffset
+= aBlockSize
;
639 // Set the start of the next block to the start of the next
640 // record, and the size of the block to 0.
641 aBlockStart
= aReadOffset
;
643 } // if (mDeletedSet..)
644 } // for (aRecord...)
646 // Copy all records that have not yet been copied to the new file.
649 inAtomicTempFile
.write(AtomicFile::FromStart
, anOffset
,
650 aRecordsSection
.range(Range(aBlockStart
,
653 anOffset
+= aBlockSize
;
657 // Now add all inserted records to the table.
658 InsertedMap::const_iterator anIt
= mInsertedMap
.begin();
659 InsertedMap::const_iterator anEnd
= mInsertedMap
.end();
660 // Iterate over all inserted objects.
661 for (; anIt
!= anEnd
; anIt
++)
663 // Write out each inserted/modified record
664 const WriteSection
&aRecord
= *anIt
->second
;
665 uint32 aRecordNumber
= anIt
->first
;
666 // Put offset relative to start of this table in recordNumber array.
667 aTableSection
.put(Table::OffsetRecordNumbers
+ AtomSize
* aRecordNumber
,
668 anOffset
- inSectionOffset
);
669 inAtomicTempFile
.write(AtomicFile::FromStart
, anOffset
,
670 aRecord
.address(), aRecord
.size());
671 anOffset
+= aRecord
.size();
673 // XXX update all indexes being created.
676 // Reconstruct the freelist (this is O(N) where N is the number of recordNumbers)
677 // We could implement it faster by using the old freelist and skipping the records
678 // that have been inserted. However building the freelist for the newly used
679 // recordNumbers (not in mTable) would look like the code below anyway (starting
680 // from mTable->recordNumberCount()).
681 // The first part of this would be O(M Log(N)) (where M is the old number of
682 // free records, and N is the number of newly inserted records)
683 // The second part would be O(N) where N is the currently max RecordNumber
684 // in use - the old max RecordNumber in use.
685 uint32 aFreeListHead
= 0; // Link to previous free record
686 for (uint32 aRecordNumber
= 0; aRecordNumber
< aRecordNumbersCount
; aRecordNumber
++)
688 // Make the freelist a list of all records with 0 offset (non existing).
689 if (!aTableSection
.at(Table::OffsetRecordNumbers
+ AtomSize
* aRecordNumber
))
691 aTableSection
.put(Table::OffsetRecordNumbers
692 + AtomSize
* aRecordNumber
,
694 // Make aFreeListHead point to the previous free recordNumber slot in the table.
695 aFreeListHead
= (Table::OffsetRecordNumbers
+ AtomSize
* aRecordNumber
) | 1;
698 aTableSection
.put(Table::OffsetFreeListHead
, aFreeListHead
);
700 anOffset
-= inSectionOffset
;
702 // Write out indexes, which are part of the table section
705 uint32 indexOffset
= anOffset
;
706 anOffset
= writeIndexSection(aTableSection
, anOffset
);
707 inAtomicTempFile
.write(AtomicFile::FromStart
, inSectionOffset
+ indexOffset
,
708 aTableSection
.address() + indexOffset
, anOffset
- indexOffset
);
711 // Set the section size and recordCount.
712 aTableSection
.put(Table::OffsetSize
, anOffset
);
713 aTableSection
.put(Table::OffsetRecordsCount
, aRecordsCount
);
715 // Write out aTableSection header.
716 inAtomicTempFile
.write(AtomicFile::FromStart
, inSectionOffset
,
717 aTableSection
.address(), aTableSection
.size());
719 return anOffset
+ inSectionOffset
;
723 #pragma clang diagnostic push
724 #pragma clang diagnostic ignored "-Wunused-const-variable"
730 // Attribute definitions
732 static const CSSM_DB_ATTRIBUTE_INFO RelationID
=
734 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
735 {(char*) "RelationID"},
736 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
738 static const CSSM_DB_ATTRIBUTE_INFO RelationName
=
740 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
741 {(char*) "RelationName"},
742 CSSM_DB_ATTRIBUTE_FORMAT_STRING
744 static const CSSM_DB_ATTRIBUTE_INFO AttributeID
=
746 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
747 {(char*) "AttributeID"},
748 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
750 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameFormat
=
752 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
753 {(char*) "AttributeNameFormat"},
754 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
756 static const CSSM_DB_ATTRIBUTE_INFO AttributeName
=
758 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
759 {(char*) "AttributeName"},
760 CSSM_DB_ATTRIBUTE_FORMAT_STRING
762 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameID
=
764 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
765 {(char*) "AttributeNameID"},
766 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
768 static const CSSM_DB_ATTRIBUTE_INFO AttributeFormat
=
770 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
771 {(char*) "AttributeFormat"},
772 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
774 static const CSSM_DB_ATTRIBUTE_INFO IndexID
=
776 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
778 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
780 static const CSSM_DB_ATTRIBUTE_INFO IndexType
=
782 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
783 {(char*) "IndexType"},
784 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
786 static const CSSM_DB_ATTRIBUTE_INFO IndexedDataLocation
=
788 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
789 {(char*) "IndexedDataLocation"},
790 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
792 static const CSSM_DB_ATTRIBUTE_INFO ModuleID
=
794 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
795 {(char*) "ModuleID"},
796 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
798 static const CSSM_DB_ATTRIBUTE_INFO AddinVersion
=
800 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
801 {(char*) "AddinVersion"},
802 CSSM_DB_ATTRIBUTE_FORMAT_STRING
804 static const CSSM_DB_ATTRIBUTE_INFO SSID
=
806 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
808 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
810 static const CSSM_DB_ATTRIBUTE_INFO SubserviceType
=
812 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
,
813 {(char*) "SubserviceType"},
814 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
817 #define ATTRIBUTE(type, name) \
818 { CSSM_DB_ATTRIBUTE_NAME_AS_STRING, { (char*) #name }, CSSM_DB_ATTRIBUTE_FORMAT_ ## type }
820 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaRelations
[] =
822 //RelationID, RelationName
823 ATTRIBUTE(UINT32
, RelationID
),
824 ATTRIBUTE(STRING
, RelationName
)
827 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaAttributes
[] =
829 //RelationID, AttributeID,
830 //AttributeNameFormat, AttributeName, AttributeNameID,
832 ATTRIBUTE(UINT32
, RelationID
),
833 ATTRIBUTE(UINT32
, AttributeID
),
834 ATTRIBUTE(UINT32
, AttributeNameFormat
),
835 ATTRIBUTE(STRING
, AttributeName
),
836 ATTRIBUTE(BLOB
, AttributeNameID
),
837 ATTRIBUTE(UINT32
, AttributeFormat
)
840 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaIndexes
[] =
842 ATTRIBUTE(UINT32
, RelationID
),
843 ATTRIBUTE(UINT32
, IndexID
),
844 ATTRIBUTE(UINT32
, AttributeID
),
845 ATTRIBUTE(UINT32
, IndexType
),
846 ATTRIBUTE(UINT32
, IndexedDataLocation
)
847 //RelationID, IndexID, AttributeID,
848 //IndexType, IndexedDataLocation
851 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaParsingModule
[] =
853 ATTRIBUTE(UINT32
, RelationID
),
854 ATTRIBUTE(UINT32
, AttributeID
),
855 ATTRIBUTE(BLOB
, ModuleID
),
856 ATTRIBUTE(STRING
, AddinVersion
),
857 ATTRIBUTE(UINT32
, SSID
),
858 ATTRIBUTE(UINT32
, SubserviceType
)
859 //RelationID, AttributeID,
860 //ModuleID, AddinVersion, SSID, SubserviceType
864 #pragma clang diagnostic pop
869 DbVersion::DbVersion(const AppleDatabase
&db
, const RefPointer
<AtomicBufferedFile
> &inAtomicBufferedFile
) :
870 mDatabase(reinterpret_cast<const uint8
*>(NULL
), 0),
872 mBufferedFile(inAtomicBufferedFile
)
874 off_t aLength
= mBufferedFile
->length();
876 const uint8
*ptr
= mBufferedFile
->read(0, aLength
, bytesRead
);
877 mBufferedFile
->close();
878 mDatabase
= ReadSection(ptr
, (size_t)bytesRead
);
882 DbVersion::~DbVersion()
886 for_each_map_delete(mTableMap
.begin(), mTableMap
.end());
896 // This is the oposite of DbModifier::commit()
897 mVersionId
= mDatabase
[mDatabase
.size() - AtomSize
];
899 const ReadSection aHeaderSection
= mDatabase
.subsection(HeaderOffset
,
901 if (aHeaderSection
.at(OffsetMagic
) != HeaderMagic
)
902 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
904 // We currently only support one version. If we support additional
905 // file format versions in the future fix this.
906 uint32 aVersion
= aHeaderSection
.at(OffsetVersion
);
907 if (aVersion
!= HeaderVersion
)
908 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
910 //const ReadSection anAuthSection =
911 // mDatabase.subsection(HeaderOffset + aHeaderSection.at(OffsetAuthOffset));
912 // XXX Do something with anAuthSection.
914 uint32 aSchemaOffset
= aHeaderSection
.at(OffsetSchemaOffset
);
915 const ReadSection aSchemaSection
=
916 mDatabase
.subsection(HeaderOffset
+ aSchemaOffset
);
918 uint32 aSchemaSize
= aSchemaSection
[OffsetSchemaSize
];
919 // Make sure that the given range exists.
920 aSchemaSection
.subsection(0, aSchemaSize
);
921 uint32 aTableCount
= aSchemaSection
[OffsetTablesCount
];
923 // Assert that the size of this section is big enough.
924 if (aSchemaSize
< OffsetTables
+ AtomSize
* aTableCount
)
925 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
927 for (uint32 aTableNumber
= 0; aTableNumber
< aTableCount
;
930 uint32 aTableOffset
= aSchemaSection
.at(OffsetTables
+ AtomSize
932 // XXX Set the size boundary on aTableSection.
933 const ReadSection aTableSection
=
934 aSchemaSection
.subsection(aTableOffset
);
935 auto_ptr
<Table
> aTable(new Table(aTableSection
));
936 Table::Id aTableId
= aTable
->getMetaRecord().dataRecordType();
937 mTableMap
.insert(TableMap::value_type(aTableId
, aTable
.get()));
941 // Fill in the schema for the meta tables.
943 findTable(mDb
.schemaRelations
.DataRecordType
).getMetaRecord().
944 setRecordAttributeInfo(mDb
.schemaRelations
);
945 findTable(mDb
.schemaIndexes
.DataRecordType
).getMetaRecord().
946 setRecordAttributeInfo(mDb
.schemaIndexes
);
947 findTable(mDb
.schemaParsingModule
.DataRecordType
).getMetaRecord().
948 setRecordAttributeInfo(mDb
.schemaParsingModule
);
950 // OK, we have created all the tables in the tableMap. Now
951 // lets read the schema and proccess it accordingly.
952 // Iterate over all schema records.
953 Table
&aTable
= findTable(mDb
.schemaAttributes
.DataRecordType
);
954 aTable
.getMetaRecord().setRecordAttributeInfo(mDb
.schemaAttributes
);
955 uint32 aRecordsCount
= aTable
.getRecordsCount();
956 ReadSection aRecordsSection
= aTable
.getRecordsSection();
957 uint32 aReadOffset
= 0;
958 const MetaRecord
&aMetaRecord
= aTable
.getMetaRecord();
960 CSSM_DB_ATTRIBUTE_DATA aRelationIDData
=
966 CSSM_DB_ATTRIBUTE_DATA aAttributeIDData
=
972 CSSM_DB_ATTRIBUTE_DATA aAttributeNameFormatData
=
978 CSSM_DB_ATTRIBUTE_DATA aAttributeNameData
=
984 CSSM_DB_ATTRIBUTE_DATA aAttributeNameIDData
=
990 CSSM_DB_ATTRIBUTE_DATA aAttributeFormatData
=
996 CSSM_DB_ATTRIBUTE_DATA aRecordAttributes
[] =
1000 aAttributeNameFormatData
,
1002 aAttributeNameIDData
,
1003 aAttributeFormatData
1005 CSSM_DB_RECORD_ATTRIBUTE_DATA aRecordAttributeData
=
1007 aMetaRecord
.dataRecordType(),
1009 sizeof(aRecordAttributes
) / sizeof(CSSM_DB_ATTRIBUTE_DATA
),
1012 CssmDbRecordAttributeData
&aRecordData
= CssmDbRecordAttributeData::overlay(aRecordAttributeData
);
1014 TrackingAllocator
recordAllocator(Allocator::standard());
1015 for (uint32 aRecord
= 0; aRecord
!= aRecordsCount
; aRecord
++)
1017 ReadSection aRecordSection
= MetaRecord::readSection(aRecordsSection
, aReadOffset
);
1018 uint32 aRecordSize
= aRecordSection
.size();
1019 aReadOffset
+= aRecordSize
;
1020 aMetaRecord
.unpackRecord(aRecordSection
, recordAllocator
,
1021 &aRecordAttributeData
, NULL
, 0);
1022 // Create the attribute coresponding to this entry
1023 if (aRecordData
[0].size() != 1 || aRecordData
[0].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
)
1024 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1025 uint32 aRelationId
= aRecordData
[0];
1027 // Skip the schema relations for the meta tables themselves.
1028 // FIXME: this hard-wires the meta-table relation IDs to be
1029 // within {CSSM_DB_RECORDTYPE_SCHEMA_START...
1030 // CSSM_DB_RECORDTYPE_SCHEMA_END} (which is {0..4}).
1031 // Bogus - the MDS schema relation IDs start at
1032 // CSSM_DB_RELATIONID_MDS_START which is 0x40000000.
1033 // Ref. Radar 2817921.
1034 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= aRelationId
&& aRelationId
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
1037 // Get the MetaRecord corresponding to the specified RelationId
1038 MetaRecord
&aMetaRecord
= findTable(aRelationId
).getMetaRecord();
1040 if (aRecordData
[1].size() != 1
1041 || aRecordData
[1].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1042 || aRecordData
[2].size() != 1
1043 || aRecordData
[2].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1044 || aRecordData
[5].size() != 1
1045 || aRecordData
[5].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
)
1046 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1048 uint32 anAttributeId
= aRecordData
[1];
1049 uint32 anAttributeNameFormat
= aRecordData
[2];
1050 uint32 anAttributeFormat
= aRecordData
[5];
1051 auto_ptr
<string
> aName
;
1052 const CssmData
*aNameID
= NULL
;
1054 if (aRecordData
[3].size() == 1)
1056 if (aRecordData
[3].format() != CSSM_DB_ATTRIBUTE_FORMAT_STRING
)
1057 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1059 auto_ptr
<string
> aName2(new string(static_cast<string
>(aRecordData
[3])));
1063 if (aRecordData
[4].size() == 1)
1065 if (aRecordData
[4].format() != CSSM_DB_ATTRIBUTE_FORMAT_BLOB
)
1066 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1068 // @@@ Invoking conversion operator to CssmData & on aRecordData[4]
1069 // And taking address of result.
1070 aNameID
= &static_cast<const CssmData
&>(aRecordData
[4]);
1073 // Make sure that the attribute specified by anAttributeNameFormat is present.
1074 switch (anAttributeNameFormat
)
1076 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING
:
1077 if (aRecordData
[3].size() != 1)
1078 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1080 case CSSM_DB_ATTRIBUTE_NAME_AS_OID
:
1081 if (aRecordData
[4].size() != 1)
1082 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1084 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
:
1087 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1090 // Create the attribute
1091 aMetaRecord
.createAttribute(aName
.get(), aNameID
, anAttributeId
, anAttributeFormat
);
1094 // initialize the indexes associated with each table
1096 TableMap::iterator it
;
1097 for (it
= mTableMap
.begin(); it
!= mTableMap
.end(); it
++)
1098 it
->second
->readIndexSection();
1103 for_each_map_delete(mTableMap
.begin(), mTableMap
.end());
1110 DbVersion::getRecord(Table::Id inTableId
, const RecordId
&inRecordId
,
1111 CSSM_DB_RECORD_ATTRIBUTE_DATA
*inoutAttributes
,
1112 CssmData
*inoutData
,
1113 Allocator
&inAllocator
) const
1115 return findTable(inTableId
).getRecord(inRecordId
, inoutAttributes
,
1116 inoutData
, inAllocator
);
1120 DbVersion::createCursor(const CSSM_QUERY
*inQuery
) const
1122 // XXX We should add support for these special query types
1123 // By Creating a Cursor that iterates over multiple tables
1124 if (!inQuery
|| inQuery
->RecordType
== CSSM_DL_DB_RECORD_ANY
1125 || inQuery
->RecordType
== CSSM_DL_DB_RECORD_ALL_KEYS
)
1127 return new MultiCursor(inQuery
, *this);
1130 return findTable(inQuery
->RecordType
).createCursor(inQuery
, *this);
1133 bool DbVersion::hasTable(Table::Id inTableId
) const
1135 TableMap::const_iterator it
= mTableMap
.find(inTableId
);
1136 return it
!= mTableMap
.end();
1140 DbVersion::findTable(Table::Id inTableId
) const
1142 TableMap::const_iterator it
= mTableMap
.find(inTableId
);
1143 if (it
== mTableMap
.end())
1144 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1149 DbVersion::findTable(Table::Id inTableId
)
1151 TableMap::iterator it
= mTableMap
.find(inTableId
);
1152 if (it
== mTableMap
.end())
1153 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1158 // Cursor implemetation
1164 Cursor::Cursor(const DbVersion
&inDbVersion
) : mDbVersion(&inDbVersion
)
1173 Cursor::next(Table::Id
&outTableId
,
1174 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes
,
1176 Allocator
&inAllocator
,
1183 // LinearCursor implemetation
1185 LinearCursor::LinearCursor(const CSSM_QUERY
*inQuery
, const DbVersion
&inDbVersion
,
1186 const Table
&inTable
) :
1187 Cursor(inDbVersion
),
1188 mRecordsCount(inTable
.getRecordsCount()),
1190 mRecordsSection(inTable
.getRecordsSection()),
1192 mMetaRecord(inTable
.getMetaRecord())
1196 mConjunctive
= inQuery
->Conjunctive
;
1197 mQueryFlags
= inQuery
->QueryFlags
;
1198 // XXX Do something with inQuery->QueryLimits?
1199 uint32 aPredicatesCount
= inQuery
->NumSelectionPredicates
;
1200 mPredicates
.resize(aPredicatesCount
);
1203 for (uint32 anIndex
= 0; anIndex
< aPredicatesCount
; anIndex
++)
1205 CSSM_SELECTION_PREDICATE
&aPredicate
= inQuery
->SelectionPredicate
[anIndex
];
1206 mPredicates
[anIndex
] = new SelectionPredicate(mMetaRecord
, aPredicate
);
1211 for_each_delete(mPredicates
.begin(), mPredicates
.end());
1217 LinearCursor::~LinearCursor()
1219 for_each_delete(mPredicates
.begin(), mPredicates
.end());
1223 LinearCursor::next(Table::Id
&outTableId
,
1224 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
1225 CssmData
*inoutData
, Allocator
&inAllocator
, RecordId
&recordId
)
1227 while (mRecord
++ < mRecordsCount
)
1229 ReadSection aRecordSection
= MetaRecord::readSection(mRecordsSection
, mReadOffset
);
1230 uint32 aRecordSize
= aRecordSection
.size();
1231 mReadOffset
+= aRecordSize
;
1233 PredicateVector::const_iterator anIt
= mPredicates
.begin();
1234 PredicateVector::const_iterator anEnd
= mPredicates
.end();
1238 // If there are no predicates we have a match.
1241 else if (mConjunctive
== CSSM_DB_OR
)
1243 // If mConjunctive is OR, the first predicate that returns
1244 // true indicates a match. Dropthough means no match
1246 for (; anIt
!= anEnd
; anIt
++)
1248 if ((*anIt
)->evaluate(aRecordSection
))
1255 else if (mConjunctive
== CSSM_DB_AND
|| mConjunctive
== CSSM_DB_NONE
)
1257 // If mConjunctive is AND (or NONE), the first predicate that returns
1258 // false indicates a mismatch. Dropthough means a match
1260 for (; anIt
!= anEnd
; anIt
++)
1262 if (!(*anIt
)->evaluate(aRecordSection
))
1271 // XXX Should be CSSMERR_DL_INVALID_QUERY (or CSSMERR_DL_INVALID_CONJUNTIVE).
1272 CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_QUERY
);
1277 // Get the actual record.
1278 mMetaRecord
.unpackRecord(aRecordSection
, inAllocator
,
1279 inoutAttributes
, inoutData
,
1281 outTableId
= mMetaRecord
.dataRecordType();
1282 recordId
= MetaRecord::unpackRecordId(aRecordSection
);
1294 IndexCursor::IndexCursor(DbQueryKey
*queryKey
, const DbVersion
&inDbVersion
,
1295 const Table
&table
, const DbConstIndex
*index
) :
1296 Cursor(inDbVersion
), mQueryKey(queryKey
), mTable(table
), mIndex(index
)
1298 index
->performQuery(*queryKey
, mBegin
, mEnd
);
1301 IndexCursor::~IndexCursor()
1303 // the query key will be deleted automatically, since it's an auto_ptr
1307 IndexCursor::next(Table::Id
&outTableId
,
1308 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes
,
1310 Allocator
&inAllocator
, RecordId
&recordId
)
1315 ReadSection rs
= mIndex
->getRecordSection(mBegin
++);
1316 const MetaRecord
&metaRecord
= mTable
.getMetaRecord();
1318 outTableId
= metaRecord
.dataRecordType();
1319 metaRecord
.unpackRecord(rs
, inAllocator
, outAttributes
, outData
, 0);
1321 recordId
= MetaRecord::unpackRecordId(rs
);
1328 MultiCursor::MultiCursor(const CSSM_QUERY
*inQuery
, const DbVersion
&inDbVersion
) :
1329 Cursor(inDbVersion
), mTableIterator(inDbVersion
.begin())
1332 mQuery
.reset(new CssmAutoQuery(*inQuery
));
1335 mQuery
.reset(new CssmAutoQuery());
1336 mQuery
->recordType(CSSM_DL_DB_RECORD_ANY
);
1340 MultiCursor::~MultiCursor()
1345 MultiCursor::next(Table::Id
&outTableId
,
1346 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
1347 CssmData
*inoutData
, Allocator
&inAllocator
, RecordId
&recordId
)
1353 if (mTableIterator
== mDbVersion
->end())
1356 const Table
&aTable
= *mTableIterator
++;
1357 if (!aTable
.matchesTableId(mQuery
->recordType()))
1360 mCursor
.reset(aTable
.createCursor(mQuery
.get(), *mDbVersion
));
1363 if (mCursor
->next(outTableId
, inoutAttributes
, inoutData
, inAllocator
, recordId
))
1366 mCursor
.reset(NULL
);
1374 DbModifier::DbModifier(AtomicFile
&inAtomicFile
, const AppleDatabase
&db
) :
1377 mAtomicFile(inAtomicFile
),
1382 DbModifier::~DbModifier()
1386 for_each_map_delete(mModifiedTableMap
.begin(), mModifiedTableMap
.end());
1387 // mAtomicTempFile will do automatic rollback on destruction.
1392 const RefPointer
<const DbVersion
>
1393 DbModifier::getDbVersion(bool force
)
1395 StLock
<Mutex
> _(mDbVersionLock
);
1397 /* Initialize the shared memory file change mechanism */
1398 pthread_once(&gCommonInitMutex
, initCommon
);
1400 /* If we don't have a mDbVersion yet, or we are force to re-read the file
1401 before a write transaction, or we have received any notifications after
1402 the last time we read the file, or more than kForceReReadTime seconds
1403 have passed since the last time we read the file, we open the file and
1404 check if it has changed. */
1408 mNotifyCount
!= *gSegment
||
1409 CFAbsoluteTimeGetCurrent() > mDbLastRead
+ kForceReReadTime
)
1411 RefPointer
<AtomicBufferedFile
> atomicBufferedFile(mAtomicFile
.read());
1412 off_t length
= atomicBufferedFile
->open();
1413 /* Record the number of notifications we've seen and when we last
1415 if (gSegment
!= NULL
)
1417 mNotifyCount
= *gSegment
;
1420 mDbLastRead
= CFAbsoluteTimeGetCurrent();
1422 /* If we already have a mDbVersion, let's check if we can reuse it. */
1425 if (length
< AtomSize
)
1426 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
1428 off_t bytesRead
= 0;
1429 const uint8
*ptr
= atomicBufferedFile
->read(length
- AtomSize
,
1430 AtomSize
, bytesRead
);
1431 ReadSection
aVersionSection(ptr
, (size_t)bytesRead
);
1432 uint32 aVersionId
= aVersionSection
[0];
1434 /* If the version stamp hasn't changed the old mDbVersion is still
1436 if (aVersionId
== mDbVersion
->getVersionId())
1440 mDbVersion
= new DbVersion(mDb
, atomicBufferedFile
);
1447 DbModifier::createDatabase(const CSSM_DBINFO
&inDbInfo
,
1448 const CSSM_ACL_ENTRY_INPUT
*inInitialAclEntry
,
1451 // XXX This needs better locking. There is a possible race condition between
1452 // two concurrent creators. Or a writer/creator or a close/create etc.
1453 if (mAtomicTempFile
|| !mModifiedTableMap
.empty())
1454 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
);
1456 mAtomicTempFile
= mAtomicFile
.create(mode
);
1457 // Set mVersionId to one since this is the first version of the database.
1460 // we need to create the meta tables first, because inserting tables
1461 // (including the meta tables themselves) relies on them being there
1462 createTable(new MetaRecord(mDb
.schemaRelations
));
1463 createTable(new MetaRecord(mDb
.schemaAttributes
));
1464 createTable(new MetaRecord(mDb
.schemaIndexes
));
1465 createTable(new MetaRecord(mDb
.schemaParsingModule
));
1467 // now add the meta-tables' schema to the meta tables themselves
1468 insertTableSchema(mDb
.schemaRelations
);
1469 insertTableSchema(mDb
.schemaAttributes
);
1470 insertTableSchema(mDb
.schemaIndexes
);
1471 insertTableSchema(mDb
.schemaParsingModule
);
1473 if (inInitialAclEntry
!= NULL
)
1475 //createACL(*inInitialAclEntry);
1478 if (inDbInfo
.NumberOfRecordTypes
== 0)
1480 if (inDbInfo
.RecordAttributeNames
== NULL
)
1481 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1482 if (inDbInfo
.RecordIndexes
== NULL
)
1483 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_INDEX
);
1484 if (inDbInfo
.DefaultParsingModules
== NULL
)
1485 CssmError::throwMe(CSSMERR_DL_INVALID_PARSING_MODULE
);
1487 for (uint32 anIndex
= 0; anIndex
< inDbInfo
.NumberOfRecordTypes
; anIndex
++)
1489 insertTable(CssmDbRecordAttributeInfo::overlay(inDbInfo
.RecordAttributeNames
[anIndex
]),
1490 &inDbInfo
.RecordIndexes
[anIndex
],
1491 &inDbInfo
.DefaultParsingModules
[anIndex
]);
1495 void DbModifier::openDatabase()
1497 // No need to do anything on open if we are already writing the database.
1498 if (!mAtomicTempFile
)
1499 getDbVersion(false);
1502 void DbModifier::closeDatabase()
1504 commit(); // XXX Requires write lock.
1505 StLock
<Mutex
> _(mDbVersionLock
);
1509 void DbModifier::deleteDatabase()
1511 bool isDirty
= mAtomicTempFile
;
1512 rollback(); // XXX Requires write lock.
1513 StLock
<Mutex
> _(mDbVersionLock
);
1515 // Clean up mModifiedTableMap in case this object gets reused again for
1517 for_each_map_delete(mModifiedTableMap
.begin(), mModifiedTableMap
.end());
1518 mModifiedTableMap
.clear();
1520 // If the database was dirty and we had no mDbVersion yet then rollback()
1521 // would have deleted the db.
1522 if (!isDirty
|| mDbVersion
)
1525 mAtomicFile
.performDelete();
1530 DbModifier::modifyDatabase()
1532 if (mAtomicTempFile
)
1537 mAtomicTempFile
= mAtomicFile
.write();
1538 // Now we are holding the write lock make sure we get the latest greatest version of the db.
1539 // Also set mVersionId to one more that that of the old database.
1540 mVersionId
= getDbVersion(true)->getVersionId() + 1;
1542 // Never make a database with mVersionId 0 since it makes bad things happen to Jaguar and older systems
1543 if (mVersionId
== 0)
1546 // Remove all old modified tables
1547 for_each_map_delete(mModifiedTableMap
.begin(), mModifiedTableMap
.end());
1548 mModifiedTableMap
.clear();
1550 // Setup the new tables
1551 DbVersion::TableMap::const_iterator anIt
=
1552 mDbVersion
->mTableMap
.begin();
1553 DbVersion::TableMap::const_iterator anEnd
=
1554 mDbVersion
->mTableMap
.end();
1555 for (; anIt
!= anEnd
; ++anIt
)
1557 auto_ptr
<ModifiedTable
> aTable(new ModifiedTable(anIt
->second
));
1558 mModifiedTableMap
.insert(ModifiedTableMap::value_type(anIt
->first
,
1565 for_each_map_delete(mModifiedTableMap
.begin(), mModifiedTableMap
.end());
1566 mModifiedTableMap
.clear();
1573 DbModifier::deleteRecord(Table::Id inTableId
, const RecordId
&inRecordId
)
1576 findTable(inTableId
).deleteRecord(inRecordId
);
1580 DbModifier::insertRecord(Table::Id inTableId
,
1581 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributes
,
1582 const CssmData
*inData
)
1585 return findTable(inTableId
).insertRecord(mVersionId
, inAttributes
, inData
);
1589 DbModifier::updateRecord(Table::Id inTableId
, const RecordId
&inRecordId
,
1590 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributes
,
1591 const CssmData
*inData
,
1592 CSSM_DB_MODIFY_MODE inModifyMode
)
1595 return findTable(inTableId
).updateRecord(inRecordId
, inAttributes
, inData
, inModifyMode
);
1598 // Create a table associated with a given metarecord, and add the table
1602 DbModifier::createTable(MetaRecord
*inMetaRecord
)
1604 auto_ptr
<MetaRecord
> aMetaRecord(inMetaRecord
);
1605 auto_ptr
<ModifiedTable
> aModifiedTable(new ModifiedTable(inMetaRecord
));
1606 // Now that aModifiedTable is fully constructed it owns inMetaRecord
1607 aMetaRecord
.release();
1609 if (!mModifiedTableMap
.insert
1610 (ModifiedTableMap::value_type(inMetaRecord
->dataRecordType(),
1611 aModifiedTable
.get())).second
)
1613 // XXX Should be CSSMERR_DL_DUPLICATE_RECORDTYPE. Since that
1614 // doesn't exist we report that the metatable's unique index would
1615 // no longer be valid
1616 CssmError::throwMe(CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA
);
1619 return aModifiedTable
.release();
1623 DbModifier::deleteTable(Table::Id inTableId
)
1626 // Can't delete schema tables.
1627 if (CSSM_DB_RECORDTYPE_SCHEMA_START
<= inTableId
1628 && inTableId
< CSSM_DB_RECORDTYPE_SCHEMA_END
)
1629 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1631 // Find the ModifiedTable and delete it
1632 ModifiedTableMap::iterator it
= mModifiedTableMap
.find(inTableId
);
1633 if (it
== mModifiedTableMap
.end())
1634 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1637 mModifiedTableMap
.erase(it
);
1641 DbModifier::writeAuthSection(uint32 inSectionOffset
)
1643 WriteSection anAuthSection
;
1645 // XXX Put real data into the authsection.
1646 uint32 anOffset
= anAuthSection
.put(0, 0);
1647 anAuthSection
.size(anOffset
);
1649 mAtomicTempFile
->write(AtomicFile::FromStart
, inSectionOffset
,
1650 anAuthSection
.address(), anAuthSection
.size());
1651 return inSectionOffset
+ anOffset
;
1655 DbModifier::writeSchemaSection(uint32 inSectionOffset
)
1657 uint32 aTableCount
= (uint32
) mModifiedTableMap
.size();
1658 WriteSection
aTableSection(Allocator::standard(),
1659 OffsetTables
+ AtomSize
* aTableCount
);
1660 // Set aTableSection to the correct size.
1661 aTableSection
.size(OffsetTables
+ AtomSize
* aTableCount
);
1662 aTableSection
.put(OffsetTablesCount
, aTableCount
);
1664 uint32 anOffset
= inSectionOffset
+ OffsetTables
+ AtomSize
* aTableCount
;
1665 ModifiedTableMap::const_iterator anIt
= mModifiedTableMap
.begin();
1666 ModifiedTableMap::const_iterator anEnd
= mModifiedTableMap
.end();
1667 for (uint32 aTableNumber
= 0; anIt
!= anEnd
; anIt
++, aTableNumber
++)
1669 // Put the offset to the current table relative to the start of
1670 // this section into the tables array
1671 aTableSection
.put(OffsetTables
+ AtomSize
* aTableNumber
,
1672 anOffset
- inSectionOffset
);
1673 anOffset
= anIt
->second
->writeTable(*mAtomicTempFile
, anOffset
);
1676 aTableSection
.put(OffsetSchemaSize
, anOffset
- inSectionOffset
);
1677 mAtomicTempFile
->write(AtomicFile::FromStart
, inSectionOffset
,
1678 aTableSection
.address(), aTableSection
.size());
1684 DbModifier::commit()
1686 if (!mAtomicTempFile
)
1690 secinfo("integrity", "committing to %s", mAtomicFile
.path().c_str());
1692 WriteSection
aHeaderSection(Allocator::standard(), size_t(HeaderSize
));
1693 // Set aHeaderSection to the correct size.
1694 aHeaderSection
.size(HeaderSize
);
1696 // Start writing sections after the header
1697 uint32 anOffset
= HeaderOffset
+ HeaderSize
;
1699 // Write auth section
1700 aHeaderSection
.put(OffsetAuthOffset
, anOffset
);
1701 anOffset
= writeAuthSection(anOffset
);
1702 // Write schema section
1703 aHeaderSection
.put(OffsetSchemaOffset
, anOffset
);
1704 anOffset
= writeSchemaSection(anOffset
);
1706 // Write out the file header.
1707 aHeaderSection
.put(OffsetMagic
, HeaderMagic
);
1708 aHeaderSection
.put(OffsetVersion
, HeaderVersion
);
1709 mAtomicTempFile
->write(AtomicFile::FromStart
, HeaderOffset
,
1710 aHeaderSection
.address(), aHeaderSection
.size());
1712 // Write out the versionId.
1713 WriteSection
aVersionSection(Allocator::standard(), size_t(AtomSize
));
1714 anOffset
= aVersionSection
.put(0, mVersionId
);
1715 aVersionSection
.size(anOffset
);
1717 mAtomicTempFile
->write(AtomicFile::FromEnd
, 0,
1718 aVersionSection
.address(), aVersionSection
.size());
1720 mAtomicTempFile
->commit();
1721 mAtomicTempFile
= NULL
;
1722 /* Initialize the shared memory file change mechanism */
1723 pthread_once(&gCommonInitMutex
, initCommon
);
1725 if (gSegment
!= NULL
)
1730 The following operation is endian safe because we are not looking
1731 for monotonic increase. I have tested every possible value of
1732 *gSegment, and there is no value for which alternating
1733 big and little endian increments will produce the original value.
1736 OSAtomicIncrement32Barrier (gSegment
);
1747 DbModifier::rollback() throw()
1749 // This will destroy the AtomicTempFile if we have one causing it to rollback.
1750 mAtomicTempFile
= NULL
;
1754 DbModifier::getRecord(Table::Id inTableId
, const RecordId
&inRecordId
,
1755 CSSM_DB_RECORD_ATTRIBUTE_DATA
*inoutAttributes
,
1756 CssmData
*inoutData
, Allocator
&inAllocator
)
1758 if (mAtomicTempFile
)
1760 // We are in the midst of changing the database.
1761 return findTable(inTableId
).getRecord(inRecordId
, inoutAttributes
,
1762 inoutData
, inAllocator
);
1766 return getDbVersion(false)->getRecord(inTableId
, inRecordId
,
1767 inoutAttributes
, inoutData
, inAllocator
);
1772 DbModifier::createCursor(const CSSM_QUERY
*inQuery
)
1774 if (mAtomicTempFile
)
1776 // We are modifying this database.
1778 // If we have a mDbVersion already then it's a snapshot of the database
1779 // right before the modifications started. So return a cursor using
1782 return mDbVersion
->createCursor(inQuery
);
1784 // This is a newly created but never commited database. Return a
1785 // Cursor that will not return any matches.
1786 return new Cursor();
1789 // Get the latest and greatest version of the db and create the cursor
1791 return getDbVersion(false)->createCursor(inQuery
);
1794 // Insert schema records for a new table into the metatables of the database. This gets
1795 // called while a database is being created.
1798 DbModifier::insertTableSchema(const CssmDbRecordAttributeInfo
&inInfo
,
1799 const CSSM_DB_RECORD_INDEX_INFO
*inIndexInfo
/* = NULL */)
1801 ModifiedTable
&aTable
= findTable(inInfo
.DataRecordType
);
1802 const MetaRecord
&aMetaRecord
= aTable
.getMetaRecord();
1804 CssmAutoDbRecordAttributeData
aRecordBuilder(5); // Set capacity to 5 so we don't need to grow
1806 // Create the entry for the SchemaRelations table.
1807 aRecordBuilder
.add(RelationID
, inInfo
.recordType());
1808 aRecordBuilder
.add(RelationName
, mDb
.recordName(inInfo
.recordType()));
1810 // Insert the record into the SchemaRelations ModifiedTable
1811 findTable(mDb
.schemaRelations
.DataRecordType
).insertRecord(mVersionId
,
1812 &aRecordBuilder
, NULL
);
1814 ModifiedTable
&anAttributeTable
= findTable(mDb
.schemaAttributes
.DataRecordType
);
1815 for (uint32 anIndex
= 0; anIndex
< inInfo
.size(); anIndex
++)
1817 // Create an entry for the SchemaAttributes table.
1818 aRecordBuilder
.clear();
1819 aRecordBuilder
.add(RelationID
, inInfo
.recordType());
1820 aRecordBuilder
.add(AttributeNameFormat
, inInfo
.at(anIndex
).nameFormat());
1822 uint32 attributeId
= aMetaRecord
.metaAttribute(inInfo
.at(anIndex
)).attributeId();
1824 switch (inInfo
.at(anIndex
).nameFormat())
1826 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING
:
1827 aRecordBuilder
.add(AttributeName
, inInfo
.at(anIndex
).Label
.AttributeName
);
1829 case CSSM_DB_ATTRIBUTE_NAME_AS_OID
:
1830 aRecordBuilder
.add(AttributeNameID
, inInfo
.at(anIndex
).Label
.AttributeOID
);
1832 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
:
1835 CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME
);
1838 aRecordBuilder
.add(AttributeID
, attributeId
);
1839 aRecordBuilder
.add(AttributeFormat
, inInfo
.at(anIndex
).format());
1841 // Insert the record into the SchemaAttributes ModifiedTable
1842 anAttributeTable
.insertRecord(mVersionId
, &aRecordBuilder
, NULL
);
1845 if (inIndexInfo
!= NULL
) {
1847 if (inIndexInfo
->DataRecordType
!= inInfo
.DataRecordType
&&
1848 inIndexInfo
->NumberOfIndexes
> 0)
1849 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1851 ModifiedTable
&indexMetaTable
= findTable(mDb
.schemaIndexes
.DataRecordType
);
1852 uint32 aNumberOfIndexes
= inIndexInfo
->NumberOfIndexes
;
1854 for (uint32 anIndex
= 0; anIndex
< aNumberOfIndexes
; anIndex
++)
1856 const CssmDbIndexInfo
&thisIndex
= CssmDbIndexInfo::overlay(inIndexInfo
->IndexInfo
[anIndex
]);
1858 // make sure the index is supported
1859 if (thisIndex
.dataLocation() != CSSM_DB_INDEX_ON_ATTRIBUTE
)
1860 CssmError::throwMe(CSSMERR_DL_INVALID_INDEX_INFO
);
1862 // assign an index ID: the unique index is ID 0, all others are ID > 0
1864 if (thisIndex
.IndexType
== CSSM_DB_INDEX_UNIQUE
)
1867 indexId
= anIndex
+ 1;
1869 // figure out the attribute ID
1870 uint32 attributeId
=
1871 aMetaRecord
.metaAttribute(thisIndex
.Info
).attributeId();
1873 // Create an entry for the SchemaIndexes table.
1874 aRecordBuilder
.clear();
1875 aRecordBuilder
.add(RelationID
, inInfo
.DataRecordType
);
1876 aRecordBuilder
.add(IndexID
, indexId
);
1877 aRecordBuilder
.add(AttributeID
, attributeId
);
1878 aRecordBuilder
.add(IndexType
, thisIndex
.IndexType
);
1879 aRecordBuilder
.add(IndexedDataLocation
, thisIndex
.IndexedDataLocation
);
1881 // Insert the record into the SchemaIndexes ModifiedTable
1882 indexMetaTable
.insertRecord(mVersionId
, &aRecordBuilder
, NULL
);
1884 // update the table's index objects
1885 DbMutableIndex
&index
= aTable
.findIndex(indexId
, aMetaRecord
, indexId
== 0);
1886 index
.appendAttribute(attributeId
);
1891 // Insert a new table. The attribute info is required; the index and parsing module
1892 // descriptions are optional. This version gets called during the creation of a
1896 DbModifier::insertTable(const CssmDbRecordAttributeInfo
&inInfo
,
1897 const CSSM_DB_RECORD_INDEX_INFO
*inIndexInfo
/* = NULL */,
1898 const CSSM_DB_PARSING_MODULE_INFO
*inParsingModule
/* = NULL */)
1901 createTable(new MetaRecord(inInfo
));
1902 insertTableSchema(inInfo
, inIndexInfo
);
1905 // Insert a new table. This is the version that gets called when a table is added
1906 // after a database has been created.
1909 DbModifier::insertTable(Table::Id inTableId
, const string
&inTableName
,
1910 uint32 inNumberOfAttributes
,
1911 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO
*inAttributeInfo
,
1912 uint32 inNumberOfIndexes
,
1913 const CSSM_DB_SCHEMA_INDEX_INFO
*inIndexInfo
)
1916 ModifiedTable
*aTable
= createTable(new MetaRecord(inTableId
, inNumberOfAttributes
, inAttributeInfo
));
1918 CssmAutoDbRecordAttributeData
aRecordBuilder(6); // Set capacity to 6 so we don't need to grow
1920 // Create the entry for the SchemaRelations table.
1921 aRecordBuilder
.add(RelationID
, inTableId
);
1922 aRecordBuilder
.add(RelationName
, inTableName
);
1924 // Insert the record into the SchemaRelations ModifiedTable
1925 findTable(mDb
.schemaRelations
.DataRecordType
).insertRecord(mVersionId
,
1926 &aRecordBuilder
, NULL
);
1928 ModifiedTable
&anAttributeTable
= findTable(mDb
.schemaAttributes
.DataRecordType
);
1929 for (uint32 anIndex
= 0; anIndex
< inNumberOfAttributes
; anIndex
++)
1931 // Create an entry for the SchemaAttributes table.
1932 aRecordBuilder
.clear();
1933 aRecordBuilder
.add(RelationID
, inTableId
);
1934 // XXX What should this be? We set it to CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER for now
1935 // since the AttributeID is always valid.
1936 aRecordBuilder
.add(AttributeNameFormat
, uint32(CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER
));
1937 aRecordBuilder
.add(AttributeID
, inAttributeInfo
[anIndex
].AttributeId
);
1938 if (inAttributeInfo
[anIndex
].AttributeName
)
1939 aRecordBuilder
.add(AttributeName
, inAttributeInfo
[anIndex
].AttributeName
);
1940 if (inAttributeInfo
[anIndex
].AttributeNameID
.Length
> 0)
1941 aRecordBuilder
.add(AttributeNameID
, inAttributeInfo
[anIndex
].AttributeNameID
);
1942 aRecordBuilder
.add(AttributeFormat
, inAttributeInfo
[anIndex
].DataType
);
1944 // Insert the record into the SchemaAttributes ModifiedTable
1945 anAttributeTable
.insertRecord(mVersionId
, &aRecordBuilder
, NULL
);
1948 ModifiedTable
&anIndexTable
= findTable(mDb
.schemaIndexes
.DataRecordType
);
1949 for (uint32 anIndex
= 0; anIndex
< inNumberOfIndexes
; anIndex
++)
1951 // Create an entry for the SchemaIndexes table.
1952 aRecordBuilder
.clear();
1953 aRecordBuilder
.add(RelationID
, inTableId
);
1954 aRecordBuilder
.add(IndexID
, inIndexInfo
[anIndex
].IndexId
);
1955 aRecordBuilder
.add(AttributeID
, inIndexInfo
[anIndex
].AttributeId
);
1956 aRecordBuilder
.add(IndexType
, inIndexInfo
[anIndex
].IndexType
);
1957 aRecordBuilder
.add(IndexedDataLocation
, inIndexInfo
[anIndex
].IndexedDataLocation
);
1959 // Insert the record into the SchemaIndexes ModifiedTable
1960 anIndexTable
.insertRecord(mVersionId
, &aRecordBuilder
, NULL
);
1962 // update the table's index objects
1963 DbMutableIndex
&index
= aTable
->findIndex(inIndexInfo
[anIndex
].IndexId
,
1964 aTable
->getMetaRecord(), inIndexInfo
[anIndex
].IndexType
== CSSM_DB_INDEX_UNIQUE
);
1965 index
.appendAttribute(inIndexInfo
[anIndex
].AttributeId
);
1971 bool DbModifier::hasTable(Table::Id inTableId
)
1973 return getDbVersion(false)->hasTable(inTableId
);
1979 DbModifier::findTable(Table::Id inTableId
)
1981 ModifiedTableMap::iterator it
= mModifiedTableMap
.find(inTableId
);
1982 if (it
== mModifiedTableMap
.end())
1983 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
1989 // AppleDatabaseManager implementation
1992 AppleDatabaseManager::AppleDatabaseManager(const AppleDatabaseTableName
*tableNames
)
1993 : DatabaseManager(),
1994 mTableNames(tableNames
)
1996 // make sure that a proper set of table ids and names has been provided
1999 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
2002 for (i
= 0; mTableNames
[i
].mTableName
; i
++) {}
2003 if (i
< AppleDatabaseTableName::kNumRequiredTableNames
)
2004 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
2009 AppleDatabaseManager::make(const DbName
&inDbName
)
2011 return new AppleDatabase(inDbName
, mTableNames
);
2016 // AppleDbContext implementation
2019 /* This is the version 0 CSSM_APPLEDL_OPEN_PARAMETERS struct used up to 10.2.x. */
2022 typedef struct cssm_appledl_open_parameters_v0
2024 uint32 length
; /* Should be sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0). */
2025 uint32 version
; /* Should be 0. */
2026 CSSM_BOOL autoCommit
;
2027 } CSSM_APPLEDL_OPEN_PARAMETERS_V0
;
2031 AppleDbContext::AppleDbContext(Database
&inDatabase
,
2032 DatabaseSession
&inDatabaseSession
,
2033 CSSM_DB_ACCESS_TYPE inAccessRequest
,
2034 const AccessCredentials
*inAccessCred
,
2035 const void *inOpenParameters
) :
2036 DbContext(inDatabase
, inDatabaseSession
, inAccessRequest
, inAccessCred
),
2040 const CSSM_APPLEDL_OPEN_PARAMETERS
*anOpenParameters
=
2041 reinterpret_cast<const CSSM_APPLEDL_OPEN_PARAMETERS
*>(inOpenParameters
);
2043 if (anOpenParameters
)
2045 switch (anOpenParameters
->version
)
2048 if (anOpenParameters
->length
< sizeof(CSSM_APPLEDL_OPEN_PARAMETERS
))
2049 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS
);
2051 if (anOpenParameters
->mask
& kCSSM_APPLEDL_MASK_MODE
)
2052 mMode
= anOpenParameters
->mode
;
2055 if (anOpenParameters
->length
< sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0
))
2056 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS
);
2058 mAutoCommit
= anOpenParameters
->autoCommit
== CSSM_FALSE
? false : true;
2062 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS
);
2067 AppleDbContext::~AppleDbContext()
2072 // AppleDatabase implementation
2074 AppleDatabase::AppleDatabase(const DbName
&inDbName
, const AppleDatabaseTableName
*tableNames
) :
2076 schemaRelations(tableNames
[AppleDatabaseTableName::kSchemaInfo
].mTableId
,
2077 sizeof(AttrSchemaRelations
) / sizeof(CSSM_DB_ATTRIBUTE_INFO
),
2078 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR
>(AttrSchemaRelations
)),
2079 schemaAttributes(tableNames
[AppleDatabaseTableName::kSchemaAttributes
].mTableId
,
2080 sizeof(AttrSchemaAttributes
) / sizeof(CSSM_DB_ATTRIBUTE_INFO
),
2081 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR
>(AttrSchemaAttributes
)),
2082 schemaIndexes(tableNames
[AppleDatabaseTableName::kSchemaIndexes
].mTableId
,
2083 sizeof(AttrSchemaIndexes
) / sizeof(CSSM_DB_ATTRIBUTE_INFO
),
2084 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR
>(AttrSchemaIndexes
)),
2085 schemaParsingModule(tableNames
[AppleDatabaseTableName::kSchemaParsingModule
].mTableId
,
2086 sizeof(AttrSchemaParsingModule
) / sizeof(CSSM_DB_ATTRIBUTE_INFO
),
2087 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR
>(AttrSchemaParsingModule
)),
2088 mAtomicFile(mDbName
.dbName()),
2089 mDbModifier(mAtomicFile
, *this),
2090 mTableNames(tableNames
)
2092 /* temp check for X509Anchors access - this should removed before Leopard GM */
2093 if(!strcmp(inDbName
.dbName(), "/System/Library/Keychains/X509Anchors")) {
2094 Syslog::alert("Warning: accessing obsolete X509Anchors.");
2098 AppleDatabase::~AppleDatabase()
2102 // Return the name of a record type. This uses a table that maps record types
2103 // to record names. The table is provided when the database is created.
2105 const char *AppleDatabase::recordName(CSSM_DB_RECORDTYPE inRecordType
) const
2107 if (inRecordType
== CSSM_DL_DB_RECORD_ANY
|| inRecordType
== CSSM_DL_DB_RECORD_ALL_KEYS
)
2108 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE
);
2110 for (uint32 i
= 0; mTableNames
[i
].mTableName
; i
++)
2111 if (mTableNames
[i
].mTableId
== inRecordType
)
2112 return mTableNames
[i
].mTableName
;
2118 AppleDatabase::makeDbContext(DatabaseSession
&inDatabaseSession
,
2119 CSSM_DB_ACCESS_TYPE inAccessRequest
,
2120 const AccessCredentials
*inAccessCred
,
2121 const void *inOpenParameters
)
2123 return new AppleDbContext(*this, inDatabaseSession
, inAccessRequest
,
2124 inAccessCred
, inOpenParameters
);
2128 AppleDatabase::dbCreate(DbContext
&inDbContext
, const CSSM_DBINFO
&inDBInfo
,
2129 const CSSM_ACL_ENTRY_INPUT
*inInitialAclEntry
)
2131 AppleDbContext
&context
= safer_cast
<AppleDbContext
&>(inDbContext
);
2134 StLock
<Mutex
> _(mWriteLock
);
2135 mDbModifier
.createDatabase(inDBInfo
, inInitialAclEntry
, context
.mode());
2139 mDbModifier
.rollback();
2142 if (context
.autoCommit())
2143 mDbModifier
.commit();
2147 AppleDatabase::dbOpen(DbContext
&inDbContext
)
2149 mDbModifier
.openDatabase();
2153 AppleDatabase::dbClose()
2155 StLock
<Mutex
> _(mWriteLock
);
2156 mDbModifier
.closeDatabase();
2160 AppleDatabase::dbDelete(DatabaseSession
&inDatabaseSession
,
2161 const AccessCredentials
*inAccessCred
)
2163 StLock
<Mutex
> _(mWriteLock
);
2164 // XXX Check callers credentials.
2165 mDbModifier
.deleteDatabase();
2169 AppleDatabase::createRelation(DbContext
&inDbContext
,
2170 CSSM_DB_RECORDTYPE inRelationID
,
2171 const char *inRelationName
,
2172 uint32 inNumberOfAttributes
,
2173 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO
*inAttributeInfo
,
2174 uint32 inNumberOfIndexes
,
2175 const CSSM_DB_SCHEMA_INDEX_INFO
&inIndexInfo
)
2179 StLock
<Mutex
> _(mWriteLock
);
2180 // XXX Fix the refs here.
2181 mDbModifier
.insertTable(inRelationID
, inRelationName
,
2182 inNumberOfAttributes
, inAttributeInfo
,
2183 inNumberOfIndexes
, &inIndexInfo
);
2187 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2188 mDbModifier
.rollback();
2191 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2192 mDbModifier
.commit();
2196 AppleDatabase::destroyRelation(DbContext
&inDbContext
,
2197 CSSM_DB_RECORDTYPE inRelationID
)
2201 StLock
<Mutex
> _(mWriteLock
);
2202 mDbModifier
.deleteTable(inRelationID
);
2206 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2207 mDbModifier
.rollback();
2210 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2211 mDbModifier
.commit();
2215 AppleDatabase::authenticate(DbContext
&inDbContext
,
2216 CSSM_DB_ACCESS_TYPE inAccessRequest
,
2217 const AccessCredentials
&inAccessCred
)
2219 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2223 AppleDatabase::getDbAcl(DbContext
&inDbContext
,
2224 const CSSM_STRING
*inSelectionTag
,
2225 uint32
&outNumberOfAclInfos
,
2226 CSSM_ACL_ENTRY_INFO_PTR
&outAclInfos
)
2228 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2232 AppleDatabase::changeDbAcl(DbContext
&inDbContext
,
2233 const AccessCredentials
&inAccessCred
,
2234 const CSSM_ACL_EDIT
&inAclEdit
)
2236 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2240 AppleDatabase::getDbOwner(DbContext
&inDbContext
,
2241 CSSM_ACL_OWNER_PROTOTYPE
&outOwner
)
2243 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2247 AppleDatabase::changeDbOwner(DbContext
&inDbContext
,
2248 const AccessCredentials
&inAccessCred
,
2249 const CSSM_ACL_OWNER_PROTOTYPE
&inNewOwner
)
2251 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2255 AppleDatabase::getDbNameFromHandle(const DbContext
&inDbContext
) const
2257 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2260 CSSM_DB_UNIQUE_RECORD_PTR
2261 AppleDatabase::dataInsert(DbContext
&inDbContext
,
2262 CSSM_DB_RECORDTYPE inRecordType
,
2263 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributes
,
2264 const CssmData
*inData
)
2266 CSSM_DB_UNIQUE_RECORD_PTR anUniqueRecordPtr
= NULL
;
2269 StLock
<Mutex
> _(mWriteLock
);
2270 const RecordId aRecordId
=
2271 mDbModifier
.insertRecord(inRecordType
, inAttributes
, inData
);
2273 anUniqueRecordPtr
= createUniqueRecord(inDbContext
, inRecordType
,
2275 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2276 mDbModifier
.commit();
2280 if (anUniqueRecordPtr
!= NULL
)
2281 freeUniqueRecord(inDbContext
, *anUniqueRecordPtr
);
2283 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2284 mDbModifier
.rollback();
2288 return anUniqueRecordPtr
;
2292 AppleDatabase::dataDelete(DbContext
&inDbContext
,
2293 const CSSM_DB_UNIQUE_RECORD
&inUniqueRecord
)
2297 StLock
<Mutex
> _(mWriteLock
);
2299 const RecordId
aRecordId(parseUniqueRecord(inUniqueRecord
, aTableId
));
2300 mDbModifier
.deleteRecord(aTableId
, aRecordId
);
2304 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2305 mDbModifier
.rollback();
2309 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2310 mDbModifier
.commit();
2314 AppleDatabase::dataModify(DbContext
&inDbContext
,
2315 CSSM_DB_RECORDTYPE inRecordType
,
2316 CSSM_DB_UNIQUE_RECORD
&inoutUniqueRecord
,
2317 const CSSM_DB_RECORD_ATTRIBUTE_DATA
*inAttributesToBeModified
,
2318 const CssmData
*inDataToBeModified
,
2319 CSSM_DB_MODIFY_MODE inModifyMode
)
2323 StLock
<Mutex
> _(mWriteLock
);
2325 const RecordId oldRecordId
= parseUniqueRecord(inoutUniqueRecord
,
2328 if (inRecordType
!= aTableId
)
2330 if (inRecordType
!= aTableId
&&
2331 inRecordType
!= CSSM_DL_DB_RECORD_ANY
&&
2332 !(inRecordType
== CSSM_DL_DB_RECORD_ALL_KEYS
&&
2333 (aTableId
== CSSM_DL_DB_RECORD_PUBLIC_KEY
||
2334 aTableId
== CSSM_DL_DB_RECORD_PRIVATE_KEY
||
2335 aTableId
== CSSM_DL_DB_RECORD_SYMMETRIC_KEY
)))
2338 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID
);
2341 const RecordId newRecordId
=
2342 mDbModifier
.updateRecord(aTableId
,
2344 inAttributesToBeModified
,
2347 updateUniqueRecord(inDbContext
, aTableId
, newRecordId
,
2352 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2353 mDbModifier
.rollback();
2357 if (safer_cast
<AppleDbContext
&>(inDbContext
).autoCommit())
2358 mDbModifier
.commit();
2362 AppleDatabase::dataGetFirst(DbContext
&inDbContext
,
2363 const CssmQuery
*inQuery
,
2364 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
2365 CssmData
*inoutData
,
2366 CSSM_DB_UNIQUE_RECORD_PTR
&outUniqueRecord
)
2368 // XXX: register Cursor with DbContext and have DbContext call
2369 // dataAbortQuery for all outstanding Query objects on close.
2370 auto_ptr
<Cursor
> aCursor(mDbModifier
.createCursor(inQuery
));
2374 if (!aCursor
->next(aTableId
, inoutAttributes
, inoutData
,
2375 inDbContext
.mDatabaseSession
, aRecordId
))
2376 // return a NULL handle, and implicitly delete the cursor
2377 return CSSM_INVALID_HANDLE
;
2379 outUniqueRecord
= createUniqueRecord(inDbContext
, aTableId
, aRecordId
);
2380 return aCursor
.release()->handle(); // We didn't throw so keep the Cursor around.
2384 AppleDatabase::dataGetNext(DbContext
&inDbContext
,
2385 CSSM_HANDLE inResultsHandle
,
2386 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
2387 CssmData
*inoutData
,
2388 CSSM_DB_UNIQUE_RECORD_PTR
&outUniqueRecord
)
2390 auto_ptr
<Cursor
> aCursor(&HandleObject::find
<Cursor
>(inResultsHandle
, CSSMERR_DL_INVALID_RESULTS_HANDLE
));
2394 if (!aCursor
->next(aTableId
, inoutAttributes
, inoutData
, inDbContext
.mDatabaseSession
, aRecordId
))
2397 outUniqueRecord
= createUniqueRecord(inDbContext
, aTableId
, aRecordId
);
2404 AppleDatabase::dataAbortQuery(DbContext
&inDbContext
,
2405 CSSM_HANDLE inResultsHandle
)
2407 delete &HandleObject::find
<Cursor
>(inResultsHandle
, CSSMERR_DL_INVALID_RESULTS_HANDLE
);
2411 AppleDatabase::dataGetFromUniqueRecordId(DbContext
&inDbContext
,
2412 const CSSM_DB_UNIQUE_RECORD
&inUniqueRecord
,
2413 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes
,
2414 CssmData
*inoutData
)
2417 const RecordId
aRecordId(parseUniqueRecord(inUniqueRecord
, aTableId
));
2418 // XXX Change CDSA spec to use new RecordId returned by this function
2419 mDbModifier
.getRecord(aTableId
, aRecordId
, inoutAttributes
, inoutData
,
2420 inDbContext
.mDatabaseSession
);
2424 AppleDatabase::freeUniqueRecord(DbContext
&inDbContext
,
2425 CSSM_DB_UNIQUE_RECORD
&inUniqueRecord
)
2427 if (inUniqueRecord
.RecordIdentifier
.Length
!= 0
2428 && inUniqueRecord
.RecordIdentifier
.Data
!= NULL
)
2430 inUniqueRecord
.RecordIdentifier
.Length
= 0;
2431 inDbContext
.mDatabaseSession
.free(inUniqueRecord
.RecordIdentifier
.Data
);
2433 inDbContext
.mDatabaseSession
.free(&inUniqueRecord
);
2437 AppleDatabase::updateUniqueRecord(DbContext
&inDbContext
,
2438 CSSM_DB_RECORDTYPE inTableId
,
2439 const RecordId
&inRecordId
,
2440 CSSM_DB_UNIQUE_RECORD
&inoutUniqueRecord
)
2442 uint32
*aBuffer
= reinterpret_cast<uint32
*>(inoutUniqueRecord
.RecordIdentifier
.Data
);
2443 aBuffer
[0] = inTableId
;
2444 aBuffer
[1] = inRecordId
.mRecordNumber
;
2445 aBuffer
[2] = inRecordId
.mCreateVersion
;
2446 aBuffer
[3] = inRecordId
.mRecordVersion
;
2449 CSSM_DB_UNIQUE_RECORD_PTR
2450 AppleDatabase::createUniqueRecord(DbContext
&inDbContext
,
2451 CSSM_DB_RECORDTYPE inTableId
,
2452 const RecordId
&inRecordId
)
2454 CSSM_DB_UNIQUE_RECORD_PTR aUniqueRecord
=
2455 inDbContext
.mDatabaseSession
.alloc
<CSSM_DB_UNIQUE_RECORD
>();
2456 memset(aUniqueRecord
, 0, sizeof(*aUniqueRecord
));
2457 aUniqueRecord
->RecordIdentifier
.Length
= sizeof(uint32
) * 4;
2460 aUniqueRecord
->RecordIdentifier
.Data
=
2461 inDbContext
.mDatabaseSession
.alloc
<uint8
>(sizeof(uint32
) * 4);
2462 updateUniqueRecord(inDbContext
, inTableId
, inRecordId
, *aUniqueRecord
);
2466 inDbContext
.mDatabaseSession
.free(aUniqueRecord
);
2470 return aUniqueRecord
;
2474 AppleDatabase::parseUniqueRecord(const CSSM_DB_UNIQUE_RECORD
&inUniqueRecord
,
2475 CSSM_DB_RECORDTYPE
&outTableId
)
2477 if (inUniqueRecord
.RecordIdentifier
.Length
!= sizeof(uint32
) * 4)
2478 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID
);
2480 uint32
*aBuffer
= reinterpret_cast<uint32
*>(inUniqueRecord
.RecordIdentifier
.Data
);
2481 outTableId
= aBuffer
[0];
2482 return RecordId(aBuffer
[1], aBuffer
[2], aBuffer
[3]);
2486 AppleDatabase::passThrough(DbContext
&dbContext
,
2487 uint32 passThroughId
,
2488 const void *inputParams
,
2489 void **outputParams
)
2491 switch (passThroughId
)
2493 case CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
:
2495 AppleDbContext
&dbc
= safer_cast
<AppleDbContext
&>(dbContext
);
2496 // Return the old state of the autoCommit flag if requested
2498 *reinterpret_cast<CSSM_BOOL
*>(outputParams
) = dbc
.autoCommit();
2499 dbc
.autoCommit(inputParams
? CSSM_TRUE
: CSSM_FALSE
);
2503 case CSSM_APPLEFILEDL_COMMIT
:
2504 mDbModifier
.commit();
2507 case CSSM_APPLEFILEDL_ROLLBACK
:
2508 mDbModifier
.rollback();
2511 case CSSM_APPLEFILEDL_TAKE_FILE_LOCK
:
2512 mDbModifier
.modifyDatabase();
2515 case CSSM_APPLEFILEDL_MAKE_BACKUP
:
2519 case CSSM_APPLEFILEDL_MAKE_COPY
:
2520 dbMakeCopy((const char *) inputParams
);
2523 case CSSM_APPLEFILEDL_DELETE_FILE
:
2527 case CSSM_APPLECSPDL_DB_RELATION_EXISTS
:
2529 CSSM_BOOL returnValue
;
2531 CSSM_DB_RECORDTYPE recordType
= *(CSSM_DB_RECORDTYPE
*) inputParams
;
2532 if (recordType
== CSSM_DL_DB_RECORD_ANY
|| recordType
== CSSM_DL_DB_RECORD_ALL_KEYS
)
2534 returnValue
= CSSM_TRUE
;
2538 returnValue
= mDbModifier
.hasTable(recordType
);
2541 *(CSSM_BOOL
*) outputParams
= returnValue
;
2546 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
2551 AppleDatabase::dbMakeBackup() {
2552 // Make a backup copy next to the current keychain, with filename pattern original.keychain_XXXXXX_backup
2553 char * filename_temp_cstr
= tempnam( mAtomicFile
.dir().c_str(), (mAtomicFile
.file() + "_").c_str() );
2554 string
filename_temp(filename_temp_cstr
);
2555 filename_temp
+= "_backup";
2557 free(filename_temp_cstr
);
2559 dbMakeCopy(filename_temp
.c_str());
2563 AppleDatabase::dbMakeCopy(const char* path
) {
2564 if(copyfile(mAtomicFile
.path().c_str(), path
, NULL
, COPYFILE_UNLINK
| COPYFILE_ALL
) < 0) {
2565 UnixError::throwMe(errno
);
2569 void AppleDatabase::dbDeleteFile() {
2570 if(unlink(mAtomicFile
.path().c_str()) < 0) {
2571 UnixError::throwMe(errno
);