]> git.saurik.com Git - apple/security.git/blob - libsecurity_filedb/lib/AppleDatabase.cpp
Security-55471.tar.gz
[apple/security.git] / libsecurity_filedb / lib / AppleDatabase.cpp
1 /*
2 * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved.
3 *
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
8 * using this file.
9 *
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.
16 */
17
18
19 //
20 // AppleDatabase.cpp - Description t.b.d.
21 //
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>
29 #include <fcntl.h>
30 #include <memory>
31 #include <libkern/OSAtomic.h>
32 #include <stdlib.h>
33 #include <sys/mman.h>
34 #include <fcntl.h>
35 #include <Security/cssmapplePriv.h>
36 #include <syslog.h>
37
38 static const char *kAppleDatabaseChanged = "com.apple.AppleDatabaseChanged";
39
40 /* Number of seconds after which we open/pread/close a db to check it's
41 version number even if we didn't get any notifications. Note that we always
42 check just after we take a write lock and whenever we get a notification
43 that any db on the system has changed. */
44 static const CFTimeInterval kForceReReadTime = 15.0;
45
46 /* Token on which we receive notifications and the pthread_once_t protecting
47 it's initialization. */
48 pthread_once_t gCommonInitMutex = PTHREAD_ONCE_INIT;
49
50 /* Global counter of how many notifications we have received and a lock to
51 protect the counter. */
52 static int kSegmentSize = 4;
53 int32_t* gSegment = NULL;
54
55 /* Registration routine for notifcations. Called inside a pthread_once(). */
56 static void initCommon(void)
57 {
58 // open the file
59 int segmentDescriptor = shm_open (kAppleDatabaseChanged, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
60 if (segmentDescriptor < 0)
61 {
62 return;
63 }
64
65 // set the segment size
66 ftruncate (segmentDescriptor, kSegmentSize);
67
68 // map it into memory
69 int32_t* tmp = (int32_t*) mmap (NULL, kSegmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, segmentDescriptor, 0);
70 close (segmentDescriptor);
71
72 if (tmp == (int32_t*) -1) // can't map the memory?
73 {
74 gSegment = NULL;
75 }
76 else
77 {
78 gSegment = tmp;
79 }
80 }
81
82 //
83 // Table
84 //
85 Table::Table(const ReadSection &inTableSection) :
86 mMetaRecord(inTableSection[OffsetId]),
87 mTableSection(inTableSection),
88 mRecordsCount(inTableSection[OffsetRecordsCount]),
89 mFreeListHead(inTableSection[OffsetFreeListHead]),
90 mRecordNumbersCount(inTableSection[OffsetRecordNumbersCount])
91 {
92 // can't easily initialize indexes here, since meta record is incomplete
93 // until much later... see DbVersion::open()
94 }
95
96 Table::~Table()
97 {
98 for_each_map_delete(mIndexMap.begin(), mIndexMap.end());
99 }
100
101 void
102 Table::readIndexSection()
103 {
104 uint32 indexSectionOffset = mTableSection.at(OffsetIndexesOffset);
105
106 uint32 numIndexes = mTableSection.at(indexSectionOffset + AtomSize);
107
108 for (uint32 i = 0; i < numIndexes; i++) {
109 uint32 indexOffset = mTableSection.at(indexSectionOffset + (i + 2) * AtomSize);
110 ReadSection indexSection(mTableSection.subsection(indexOffset));
111
112 auto_ptr<DbConstIndex> index(new DbConstIndex(*this, indexSection));
113 mIndexMap.insert(ConstIndexMap::value_type(index->indexId(), index.get()));
114 index.release();
115 }
116 }
117
118 Cursor *
119 Table::createCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion) const
120 {
121 // if an index matches the query, return a cursor which uses the index
122
123 ConstIndexMap::const_iterator it;
124 DbQueryKey *queryKey;
125
126 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
127 if (it->second->matchesQuery(*inQuery, queryKey)) {
128 IndexCursor *cursor = new IndexCursor(queryKey, inDbVersion, *this, it->second);
129 return cursor;
130 }
131
132 // otherwise, return a cursor that iterates over all table records
133
134 return new LinearCursor(inQuery, inDbVersion, *this);
135 }
136
137 const ReadSection
138 Table::getRecordSection(uint32 inRecordNumber) const
139 {
140 if (inRecordNumber >= mRecordNumbersCount)
141 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
142
143 uint32 aRecordOffset = mTableSection[OffsetRecordNumbers + AtomSize
144 * inRecordNumber];
145
146 // Check if this RecordNumber has been deleted.
147 if (aRecordOffset & 1 || aRecordOffset == 0)
148 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
149
150 return MetaRecord::readSection(mTableSection, aRecordOffset);
151 }
152
153 const RecordId
154 Table::getRecord(const RecordId &inRecordId,
155 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
156 CssmData *inoutData,
157 Allocator &inAllocator) const
158 {
159 const ReadSection aRecordSection = getRecordSection(inRecordId.mRecordNumber);
160 const RecordId aRecordId = MetaRecord::unpackRecordId(aRecordSection);
161
162 // Make sure the RecordNumber matches that in the RecordId we just retrived.
163 if (aRecordId.mRecordNumber != inRecordId.mRecordNumber)
164 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
165
166 if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
167 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
168
169 // XXX Figure out which value to pass for inQueryFlags (5th) argument
170 mMetaRecord.unpackRecord(aRecordSection, inAllocator, inoutAttributes,
171 inoutData, 0);
172 return aRecordId;
173 }
174
175 uint32
176 Table::popFreeList(uint32 &aFreeListHead) const
177 {
178 assert(aFreeListHead | 1);
179 uint32 anOffset = aFreeListHead ^ 1;
180 uint32 aRecordNumber = (anOffset - OffsetRecordNumbers) / AtomSize;
181 aFreeListHead = mTableSection[anOffset];
182 return aRecordNumber;
183 }
184
185 const ReadSection
186 Table::getRecordsSection() const
187 {
188 return mTableSection.subsection(mTableSection[OffsetRecords]);
189 }
190
191 bool
192 Table::matchesTableId(Id inTableId) const
193 {
194 Id anId = mMetaRecord.dataRecordType();
195 if (inTableId == CSSM_DL_DB_RECORD_ANY) // All non schema tables.
196 return !(CSSM_DB_RECORDTYPE_SCHEMA_START <= anId
197 && anId < CSSM_DB_RECORDTYPE_SCHEMA_END);
198
199 if (inTableId == CSSM_DL_DB_RECORD_ALL_KEYS) // All key tables.
200 return (anId == CSSM_DL_DB_RECORD_PUBLIC_KEY
201 || anId == CSSM_DL_DB_RECORD_PRIVATE_KEY
202 || anId == CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
203
204 return inTableId == anId; // Only if exact match.
205 }
206
207
208 //
209 // ModifiedTable
210 //
211 ModifiedTable::ModifiedTable(const Table *inTable) :
212 mTable(inTable),
213 mNewMetaRecord(nil),
214 mRecordNumberCount(inTable->recordNumberCount()),
215 mFreeListHead(inTable->freeListHead()),
216 mIsModified(false)
217 {
218 }
219
220 ModifiedTable::ModifiedTable(MetaRecord *inMetaRecord) :
221 mTable(nil),
222 mNewMetaRecord(inMetaRecord),
223 mRecordNumberCount(0),
224 mFreeListHead(0),
225 mIsModified(true)
226 {
227 }
228
229 ModifiedTable::~ModifiedTable()
230 {
231 for_each_map_delete(mIndexMap.begin(), mIndexMap.end());
232 for_each_map_delete(mInsertedMap.begin(), mInsertedMap.end());
233
234 delete mNewMetaRecord;
235 }
236
237 void
238 ModifiedTable::deleteRecord(const RecordId &inRecordId)
239 {
240 modifyTable();
241
242 uint32 aRecordNumber = inRecordId.mRecordNumber;
243
244 // remove the record from all the indexes
245 MutableIndexMap::iterator it;
246 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
247 it->second->removeRecord(aRecordNumber);
248
249 InsertedMap::iterator anIt = mInsertedMap.find(aRecordNumber);
250 if (anIt == mInsertedMap.end())
251 {
252 // If we have no old table than this record can not exist yet.
253 if (!mTable)
254 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
255
256 #if RECORDVERSIONCHECK
257 const RecordId aRecordId = MetaRecord::unpackRecordId(mTable->getRecordSection(aRecordNumber));
258 if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
259 CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED);
260 #endif
261
262 // Schedule the record for deletion
263 if (!mDeletedSet.insert(aRecordNumber).second)
264 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // It was already deleted
265 }
266 else
267 {
268 const RecordId aRecordId = MetaRecord::unpackRecordId(*anIt->second);
269 if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
270 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
271
272 #if RECORDVERSIONCHECK
273 if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
274 CssmError::throwMe(CSSMERR_DL_RECORD_MODIFIED);
275 #endif
276
277 // Remove the inserted (but uncommited) record. It should already be in mDeletedSet
278 // if it existed previously in mTable.
279 delete anIt->second;
280 mInsertedMap.erase(anIt);
281 }
282 }
283
284 const RecordId
285 ModifiedTable::insertRecord(uint32 inVersionId,
286 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
287 const CssmData *inData)
288 {
289 modifyTable();
290
291 auto_ptr<WriteSection> aWriteSection(new WriteSection());
292 getMetaRecord().packRecord(*aWriteSection, inAttributes, inData);
293 uint32 aRecordNumber = nextRecordNumber();
294
295 // add the record to all the indexes; this will throw if the new record
296 // violates a unique index
297 MutableIndexMap::iterator it;
298 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
299 it->second->insertRecord(aRecordNumber, *(aWriteSection.get()));
300
301 // schedule the record for insertion
302 RecordId aRecordId(aRecordNumber, inVersionId);
303 MetaRecord::packRecordId(aRecordId, *aWriteSection);
304 mInsertedMap.insert(InsertedMap::value_type(aRecordNumber, aWriteSection.get()));
305
306 aWriteSection.release();
307
308 return aRecordId;
309 }
310
311 const RecordId
312 ModifiedTable::updateRecord(const RecordId &inRecordId,
313 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
314 const CssmData *inData,
315 CSSM_DB_MODIFY_MODE inModifyMode)
316 {
317 modifyTable();
318
319 uint32 aRecordNumber = inRecordId.mRecordNumber;
320 InsertedMap::iterator anIt = mInsertedMap.find(aRecordNumber);
321
322 // aReUpdate is true iff we are updating an already updated record.
323 bool aReUpdate = anIt != mInsertedMap.end();
324
325 // If we are not re-updating and there is no old table than this record does not exist yet.
326 if (!aReUpdate && !mTable)
327 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
328
329 const ReadSection &anOldDbRecord = aReUpdate ? *anIt->second : mTable->getRecordSection(aRecordNumber);
330 const RecordId aRecordId = MetaRecord::unpackRecordId(anOldDbRecord);
331
332 // Did someone else delete the record we are trying to update.
333 if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
334 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
335
336 #if RECORDVERSIONCHECK
337 // Is the record we that our update is based on current?
338 if (aRecordId.mRecordVersion != inRecordId.mRecordVersion)
339 CssmError::throwMe(CSSMERR_DL_STALE_UNIQUE_RECORD);
340 #endif
341
342 // Update the actual packed record.
343 auto_ptr<WriteSection> aDbRecord(new WriteSection());
344 getMetaRecord().updateRecord(anOldDbRecord, *aDbRecord,
345 CssmDbRecordAttributeData::overlay(inAttributes), inData, inModifyMode);
346
347
348 // Bump the RecordVersion of this record.
349 RecordId aNewRecordId(aRecordNumber, inRecordId.mCreateVersion, inRecordId.mRecordVersion + 1);
350 // Store the RecordVersion in the packed aDbRecord.
351 MetaRecord::packRecordId(aNewRecordId, *aDbRecord);
352
353 if (!aReUpdate && !mDeletedSet.insert(aRecordNumber).second)
354 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Record was already in mDeletedSet
355
356 // remove the original record from all the indexes
357 MutableIndexMap::iterator it;
358 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
359 it->second->removeRecord(aRecordNumber);
360
361 try
362 {
363 // Add the updated record to all the indexes; this will throw if the new record
364 // violates a unique index
365 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
366 it->second->insertRecord(aRecordNumber, *(aDbRecord.get()));
367
368 if (aReUpdate)
369 {
370 // Get rid of anOldDbRecord from the inserted map and replace it
371 // with aDbRecord.
372 delete anIt->second;
373 anIt->second = aDbRecord.get();
374 }
375 else
376 {
377 // First time though so let's just put the new value in the map.
378 mInsertedMap.insert(InsertedMap::value_type(aRecordNumber, aDbRecord.get()));
379 }
380 aDbRecord.release();
381 }
382 catch(...)
383 {
384 // We only remove aRecordNumber from mDeletedSet if we added it above.
385 if (!aReUpdate)
386 mDeletedSet.erase(aRecordNumber);
387
388 // The 2 operations below are an attempt to preserve the indices when
389 // an insert fails.
390
391 // Remove the updated record from all the indexes
392 MutableIndexMap::iterator it;
393 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
394 it->second->removeRecord(aRecordNumber);
395
396 // Add the original record back to all the indexes
397 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++)
398 it->second->insertRecord(aRecordNumber, anOldDbRecord);
399
400 throw;
401 }
402
403 return aNewRecordId;
404 }
405
406 const RecordId
407 ModifiedTable::getRecord(const RecordId &inRecordId,
408 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
409 CssmData *inoutData,
410 Allocator &inAllocator) const
411 {
412 if (mIsModified)
413 {
414 uint32 aRecordNumber = inRecordId.mRecordNumber;
415 InsertedMap::const_iterator anIt = mInsertedMap.find(aRecordNumber);
416 if (anIt != mInsertedMap.end())
417 {
418 // We found the record in mInsertedMap so we use the inserted
419 // record.
420 const ReadSection &aRecordSection = *(anIt->second);
421 const RecordId aRecordId = MetaRecord::unpackRecordId(aRecordSection);
422
423 // Make sure the RecordNumber matches that in the RecordId we just retrived.
424 if (aRecordId.mRecordNumber != aRecordNumber)
425 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
426
427 if (aRecordId.mCreateVersion != inRecordId.mCreateVersion)
428 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
429
430 // XXX Figure out which value to pass for inQueryFlags (5th) argument
431 getMetaRecord().unpackRecord(aRecordSection, inAllocator,
432 inoutAttributes, inoutData, 0);
433
434 return aRecordId;
435 }
436 else if (mDeletedSet.find(aRecordNumber) != mDeletedSet.end())
437 {
438 // If aRecordNumber was not in mInsertedMap but it was in
439 // mDeletedSet then it was deleted but not yet commited.
440 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
441 }
442 }
443
444 if (!mTable)
445 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND);
446
447 // Either this table wasn't modified yet or we didn't find aRecordNumber in
448 // mInsertedMap nor mDeletedSet so just ask mTable for it.
449 return mTable->getRecord(inRecordId, inoutAttributes, inoutData,
450 inAllocator);
451 }
452
453 uint32
454 ModifiedTable::nextRecordNumber()
455 {
456 // If we still have unused free records in mTable get the next one.
457 if (mFreeListHead)
458 return mTable->popFreeList(mFreeListHead);
459
460 // Bump up the mRecordNumberCount so we don't reuse the same one.
461 return mRecordNumberCount++;
462 }
463
464 uint32
465 ModifiedTable::recordNumberCount() const
466 {
467 uint32 anOldMax = !mTable ? 0 : mTable->recordNumberCount() - 1;
468 uint32 anInsertedMax = mInsertedMap.empty() ? 0 : mInsertedMap.rbegin()->first;
469
470 DeletedSet::reverse_iterator anIt = mDeletedSet.rbegin();
471 DeletedSet::reverse_iterator anEnd = mDeletedSet.rend();
472 for (; anIt != anEnd; anIt++)
473 {
474 if (*anIt != anOldMax || anOldMax <= anInsertedMax)
475 break;
476 anOldMax--;
477 }
478
479 return max(anOldMax,anInsertedMax) + 1;
480 }
481
482 const MetaRecord &
483 ModifiedTable::getMetaRecord() const
484 {
485 return mNewMetaRecord ? *mNewMetaRecord : mTable->getMetaRecord();
486 }
487
488 // prepare to modify the table
489
490 void
491 ModifiedTable::modifyTable()
492 {
493 if (!mIsModified) {
494 createMutableIndexes();
495 mIsModified = true;
496 }
497 }
498
499 // create mutable indexes from the read-only indexes in the underlying table
500
501 void
502 ModifiedTable::createMutableIndexes()
503 {
504 if (mTable == NULL)
505 return;
506
507 Table::ConstIndexMap::const_iterator it;
508 for (it = mTable->mIndexMap.begin(); it != mTable->mIndexMap.end(); it++) {
509 auto_ptr<DbMutableIndex> mutableIndex(new DbMutableIndex(*it->second));
510 mIndexMap.insert(MutableIndexMap::value_type(it->first, mutableIndex.get()));
511 mutableIndex.release();
512 }
513 }
514
515 // find, and create if needed, an index with the given id
516
517 DbMutableIndex &
518 ModifiedTable::findIndex(uint32 indexId, const MetaRecord &metaRecord, bool isUniqueIndex)
519 {
520 MutableIndexMap::iterator it = mIndexMap.find(indexId);
521
522 if (it == mIndexMap.end()) {
523 // create the new index
524 auto_ptr<DbMutableIndex> index(new DbMutableIndex(metaRecord, indexId, isUniqueIndex));
525 it = mIndexMap.insert(MutableIndexMap::value_type(indexId, index.get())).first;
526 index.release();
527 }
528
529 return *it->second;
530 }
531
532 uint32
533 ModifiedTable::writeIndexSection(WriteSection &tableSection, uint32 offset)
534 {
535 MutableIndexMap::iterator it;
536
537 tableSection.put(Table::OffsetIndexesOffset, offset);
538
539 // leave room for the size, to be written later
540 uint32 indexSectionOffset = offset;
541 offset += AtomSize;
542
543 offset = tableSection.put(offset, (uint32)mIndexMap.size());
544
545 // leave room for the array of offsets to the indexes
546 uint32 indexOffsetOffset = offset;
547 offset += mIndexMap.size() * AtomSize;
548
549 // write the indexes
550 for (it = mIndexMap.begin(); it != mIndexMap.end(); it++) {
551 indexOffsetOffset = tableSection.put(indexOffsetOffset, offset);
552 offset = it->second->writeIndex(tableSection, offset);
553 }
554
555 // write the total index section size
556 tableSection.put(indexSectionOffset, offset - indexSectionOffset);
557
558 return offset;
559 }
560
561 uint32
562 ModifiedTable::writeTable(AtomicTempFile &inAtomicTempFile, uint32 inSectionOffset)
563 {
564 if (mTable && !mIsModified) {
565 // the table has not been modified, so we can just dump the old table
566 // section into the new database
567
568 const ReadSection &tableSection = mTable->getTableSection();
569 uint32 tableSize = tableSection.at(Table::OffsetSize);
570
571 inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset,
572 tableSection.range(Range(0, tableSize)), tableSize);
573
574 return inSectionOffset + tableSize;
575 }
576
577 // We should have an old mTable or a mNewMetaRecord but not both.
578 assert(mTable != nil ^ mNewMetaRecord != nil);
579 const MetaRecord &aNewMetaRecord = getMetaRecord();
580
581 uint32 aRecordsCount = 0;
582 uint32 aRecordNumbersCount = recordNumberCount();
583 uint32 aRecordsOffset = Table::OffsetRecordNumbers + AtomSize * aRecordNumbersCount;
584 WriteSection aTableSection(Allocator::standard(), aRecordsOffset);
585 aTableSection.size(aRecordsOffset);
586 aTableSection.put(Table::OffsetId, aNewMetaRecord.dataRecordType());
587 aTableSection.put(Table::OffsetRecords, aRecordsOffset);
588 aTableSection.put(Table::OffsetRecordNumbersCount, aRecordNumbersCount);
589
590 uint32 anOffset = inSectionOffset + aRecordsOffset;
591
592 if (mTable)
593 {
594 // XXX Handle schema changes in the future.
595 assert(mNewMetaRecord == nil);
596
597 // We have a modified old table so copy all non deleted records
598 // The code below is rather elaborate, but this is because it attempts
599 // to copy large ranges of non deleted records with single calls
600 // to AtomicFile::write()
601 uint32 anOldRecordsCount = mTable->getRecordsCount();
602 ReadSection aRecordsSection = mTable->getRecordsSection();
603 uint32 aReadOffset = 0; // Offset of current record
604 uint32 aWriteOffset = aRecordsOffset; // Offset for current write record
605 uint32 aBlockStart = aReadOffset; // Starting point for read
606 uint32 aBlockSize = 0; // Size of block to read
607 for (uint32 aRecord = 0; aRecord < anOldRecordsCount; aRecord++)
608 {
609 ReadSection aRecordSection = MetaRecord::readSection(aRecordsSection, aReadOffset);
610 uint32 aRecordNumber = MetaRecord::unpackRecordNumber(aRecordSection);
611 uint32 aRecordSize = aRecordSection.size();
612 aReadOffset += aRecordSize;
613 if (mDeletedSet.find(aRecordNumber) == mDeletedSet.end())
614 {
615 // This record has not been deleted. Register the offset
616 // at which it will be in the new file in aTableSection.
617 aTableSection.put(Table::OffsetRecordNumbers
618 + AtomSize * aRecordNumber,
619 aWriteOffset);
620 aWriteOffset += aRecordSize;
621 aBlockSize += aRecordSize;
622 aRecordsCount++;
623 // XXX update all indexes being created.
624 }
625 else
626 {
627 // The current record has been deleted. Copy all records up
628 // to but not including the current one to the new file.
629 if (aBlockSize > 0)
630 {
631 inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
632 aRecordsSection.range(Range(aBlockStart,
633 aBlockSize)),
634 aBlockSize);
635 anOffset += aBlockSize;
636 }
637
638 // Set the start of the next block to the start of the next
639 // record, and the size of the block to 0.
640 aBlockStart = aReadOffset;
641 aBlockSize = 0;
642 } // if (mDeletedSet..)
643 } // for (aRecord...)
644
645 // Copy all records that have not yet been copied to the new file.
646 if (aBlockSize > 0)
647 {
648 inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
649 aRecordsSection.range(Range(aBlockStart,
650 aBlockSize)),
651 aBlockSize);
652 anOffset += aBlockSize;
653 }
654 } // if (mTable)
655
656 // Now add all inserted records to the table.
657 InsertedMap::const_iterator anIt = mInsertedMap.begin();
658 InsertedMap::const_iterator anEnd = mInsertedMap.end();
659 // Iterate over all inserted objects.
660 for (; anIt != anEnd; anIt++)
661 {
662 // Write out each inserted/modified record
663 const WriteSection &aRecord = *anIt->second;
664 uint32 aRecordNumber = anIt->first;
665 // Put offset relative to start of this table in recordNumber array.
666 aTableSection.put(Table::OffsetRecordNumbers + AtomSize * aRecordNumber,
667 anOffset - inSectionOffset);
668 inAtomicTempFile.write(AtomicFile::FromStart, anOffset,
669 aRecord.address(), aRecord.size());
670 anOffset += aRecord.size();
671 aRecordsCount++;
672 // XXX update all indexes being created.
673 }
674
675 // Reconstruct the freelist (this is O(N) where N is the number of recordNumbers)
676 // We could implement it faster by using the old freelist and skipping the records
677 // that have been inserted. However building the freelist for the newly used
678 // recordNumbers (not in mTable) would look like the code below anyway (starting
679 // from mTable->recordNumberCount()).
680 // The first part of this would be O(M Log(N)) (where M is the old number of
681 // free records, and N is the number of newly inserted records)
682 // The second part would be O(N) where N is the currently max RecordNumber
683 // in use - the old max RecordNumber in use.
684 uint32 aFreeListHead = 0; // Link to previous free record
685 for (uint32 aRecordNumber = 0; aRecordNumber < aRecordNumbersCount; aRecordNumber++)
686 {
687 // Make the freelist a list of all records with 0 offset (non existing).
688 if (!aTableSection.at(Table::OffsetRecordNumbers + AtomSize * aRecordNumber))
689 {
690 aTableSection.put(Table::OffsetRecordNumbers
691 + AtomSize * aRecordNumber,
692 aFreeListHead);
693 // Make aFreeListHead point to the previous free recordNumber slot in the table.
694 aFreeListHead = (Table::OffsetRecordNumbers + AtomSize * aRecordNumber) | 1;
695 }
696 }
697 aTableSection.put(Table::OffsetFreeListHead, aFreeListHead);
698
699 anOffset -= inSectionOffset;
700
701 // Write out indexes, which are part of the table section
702
703 {
704 uint32 indexOffset = anOffset;
705 anOffset = writeIndexSection(aTableSection, anOffset);
706 inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset + indexOffset,
707 aTableSection.address() + indexOffset, anOffset - indexOffset);
708 }
709
710 // Set the section size and recordCount.
711 aTableSection.put(Table::OffsetSize, anOffset);
712 aTableSection.put(Table::OffsetRecordsCount, aRecordsCount);
713
714 // Write out aTableSection header.
715 inAtomicTempFile.write(AtomicFile::FromStart, inSectionOffset,
716 aTableSection.address(), aTableSection.size());
717
718 return anOffset + inSectionOffset;
719 }
720
721
722
723 //
724 // Metadata
725 //
726
727 // Attribute definitions
728
729 static const CSSM_DB_ATTRIBUTE_INFO RelationID =
730 {
731 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
732 {(char*) "RelationID"},
733 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
734 };
735 static const CSSM_DB_ATTRIBUTE_INFO RelationName =
736 {
737 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
738 {(char*) "RelationName"},
739 CSSM_DB_ATTRIBUTE_FORMAT_STRING
740 };
741 static const CSSM_DB_ATTRIBUTE_INFO AttributeID =
742 {
743 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
744 {(char*) "AttributeID"},
745 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
746 };
747 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameFormat =
748 {
749 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
750 {(char*) "AttributeNameFormat"},
751 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
752 };
753 static const CSSM_DB_ATTRIBUTE_INFO AttributeName =
754 {
755 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
756 {(char*) "AttributeName"},
757 CSSM_DB_ATTRIBUTE_FORMAT_STRING
758 };
759 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameID =
760 {
761 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
762 {(char*) "AttributeNameID"},
763 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
764 };
765 static const CSSM_DB_ATTRIBUTE_INFO AttributeFormat =
766 {
767 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
768 {(char*) "AttributeFormat"},
769 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
770 };
771 static const CSSM_DB_ATTRIBUTE_INFO IndexID =
772 {
773 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
774 {(char*) "IndexID"},
775 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
776 };
777 static const CSSM_DB_ATTRIBUTE_INFO IndexType =
778 {
779 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
780 {(char*) "IndexType"},
781 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
782 };
783 static const CSSM_DB_ATTRIBUTE_INFO IndexedDataLocation =
784 {
785 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
786 {(char*) "IndexedDataLocation"},
787 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
788 };
789 static const CSSM_DB_ATTRIBUTE_INFO ModuleID =
790 {
791 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
792 {(char*) "ModuleID"},
793 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
794 };
795 static const CSSM_DB_ATTRIBUTE_INFO AddinVersion =
796 {
797 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
798 {(char*) "AddinVersion"},
799 CSSM_DB_ATTRIBUTE_FORMAT_STRING
800 };
801 static const CSSM_DB_ATTRIBUTE_INFO SSID =
802 {
803 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
804 {(char*) "SSID"},
805 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
806 };
807 static const CSSM_DB_ATTRIBUTE_INFO SubserviceType =
808 {
809 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
810 {(char*) "SubserviceType"},
811 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
812 };
813
814 #define ATTRIBUTE(type, name) \
815 { CSSM_DB_ATTRIBUTE_NAME_AS_STRING, { (char*) #name }, CSSM_DB_ATTRIBUTE_FORMAT_ ## type }
816
817 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaRelations[] =
818 {
819 //RelationID, RelationName
820 ATTRIBUTE(UINT32, RelationID),
821 ATTRIBUTE(STRING, RelationName)
822 };
823
824 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaAttributes[] =
825 {
826 //RelationID, AttributeID,
827 //AttributeNameFormat, AttributeName, AttributeNameID,
828 //AttributeFormat
829 ATTRIBUTE(UINT32, RelationID),
830 ATTRIBUTE(UINT32, AttributeID),
831 ATTRIBUTE(UINT32, AttributeNameFormat),
832 ATTRIBUTE(STRING, AttributeName),
833 ATTRIBUTE(BLOB, AttributeNameID),
834 ATTRIBUTE(UINT32, AttributeFormat)
835 };
836
837 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaIndexes[] =
838 {
839 ATTRIBUTE(UINT32, RelationID),
840 ATTRIBUTE(UINT32, IndexID),
841 ATTRIBUTE(UINT32, AttributeID),
842 ATTRIBUTE(UINT32, IndexType),
843 ATTRIBUTE(UINT32, IndexedDataLocation)
844 //RelationID, IndexID, AttributeID,
845 //IndexType, IndexedDataLocation
846 };
847
848 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaParsingModule[] =
849 {
850 ATTRIBUTE(UINT32, RelationID),
851 ATTRIBUTE(UINT32, AttributeID),
852 ATTRIBUTE(BLOB, ModuleID),
853 ATTRIBUTE(STRING, AddinVersion),
854 ATTRIBUTE(UINT32, SSID),
855 ATTRIBUTE(UINT32, SubserviceType)
856 //RelationID, AttributeID,
857 //ModuleID, AddinVersion, SSID, SubserviceType
858 };
859
860 #undef ATTRIBUTE
861
862 //
863 // DbVersion
864 //
865 DbVersion::DbVersion(const AppleDatabase &db, const RefPointer <AtomicBufferedFile> &inAtomicBufferedFile) :
866 mDatabase(reinterpret_cast<const uint8 *>(NULL), 0),
867 mDb(db),
868 mBufferedFile(inAtomicBufferedFile)
869 {
870 off_t aLength = mBufferedFile->length();
871 off_t bytesRead = 0;
872 const uint8 *ptr = mBufferedFile->read(0, aLength, bytesRead);
873 mBufferedFile->close();
874 mDatabase = ReadSection(ptr, (size_t)bytesRead);
875 open();
876 }
877
878 DbVersion::~DbVersion()
879 {
880 try
881 {
882 for_each_map_delete(mTableMap.begin(), mTableMap.end());
883 }
884 catch(...) {}
885 }
886
887 void
888 DbVersion::open()
889 {
890 try
891 {
892 // This is the oposite of DbModifier::commit()
893 mVersionId = mDatabase[mDatabase.size() - AtomSize];
894
895 const ReadSection aHeaderSection = mDatabase.subsection(HeaderOffset,
896 HeaderSize);
897 if (aHeaderSection.at(OffsetMagic) != HeaderMagic)
898 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
899
900 // We currently only support one version. If we support additional
901 // file format versions in the future fix this.
902 uint32 aVersion = aHeaderSection.at(OffsetVersion);
903 if (aVersion != HeaderVersion)
904 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
905
906 //const ReadSection anAuthSection =
907 // mDatabase.subsection(HeaderOffset + aHeaderSection.at(OffsetAuthOffset));
908 // XXX Do something with anAuthSection.
909
910 uint32 aSchemaOffset = aHeaderSection.at(OffsetSchemaOffset);
911 const ReadSection aSchemaSection =
912 mDatabase.subsection(HeaderOffset + aSchemaOffset);
913
914 uint32 aSchemaSize = aSchemaSection[OffsetSchemaSize];
915 // Make sure that the given range exists.
916 aSchemaSection.subsection(0, aSchemaSize);
917 uint32 aTableCount = aSchemaSection[OffsetTablesCount];
918
919 // Assert that the size of this section is big enough.
920 if (aSchemaSize < OffsetTables + AtomSize * aTableCount)
921 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
922
923 for (uint32 aTableNumber = 0; aTableNumber < aTableCount;
924 aTableNumber++)
925 {
926 uint32 aTableOffset = aSchemaSection.at(OffsetTables + AtomSize
927 * aTableNumber);
928 // XXX Set the size boundary on aTableSection.
929 const ReadSection aTableSection =
930 aSchemaSection.subsection(aTableOffset);
931 auto_ptr<Table> aTable(new Table(aTableSection));
932 Table::Id aTableId = aTable->getMetaRecord().dataRecordType();
933 mTableMap.insert(TableMap::value_type(aTableId, aTable.get()));
934 aTable.release();
935 }
936
937 // Fill in the schema for the meta tables.
938
939 findTable(mDb.schemaRelations.DataRecordType).getMetaRecord().
940 setRecordAttributeInfo(mDb.schemaRelations);
941 findTable(mDb.schemaIndexes.DataRecordType).getMetaRecord().
942 setRecordAttributeInfo(mDb.schemaIndexes);
943 findTable(mDb.schemaParsingModule.DataRecordType).getMetaRecord().
944 setRecordAttributeInfo(mDb.schemaParsingModule);
945
946 // OK, we have created all the tables in the tableMap. Now
947 // lets read the schema and proccess it accordingly.
948 // Iterate over all schema records.
949 Table &aTable = findTable(mDb.schemaAttributes.DataRecordType);
950 aTable.getMetaRecord().setRecordAttributeInfo(mDb.schemaAttributes);
951 uint32 aRecordsCount = aTable.getRecordsCount();
952 ReadSection aRecordsSection = aTable.getRecordsSection();
953 uint32 aReadOffset = 0;
954 const MetaRecord &aMetaRecord = aTable.getMetaRecord();
955
956 CSSM_DB_ATTRIBUTE_DATA aRelationIDData =
957 {
958 RelationID,
959 0,
960 NULL
961 };
962 CSSM_DB_ATTRIBUTE_DATA aAttributeIDData =
963 {
964 AttributeID,
965 0,
966 NULL
967 };
968 CSSM_DB_ATTRIBUTE_DATA aAttributeNameFormatData =
969 {
970 AttributeNameFormat,
971 0,
972 NULL
973 };
974 CSSM_DB_ATTRIBUTE_DATA aAttributeNameData =
975 {
976 AttributeName,
977 0,
978 NULL
979 };
980 CSSM_DB_ATTRIBUTE_DATA aAttributeNameIDData =
981 {
982 AttributeNameID,
983 0,
984 NULL
985 };
986 CSSM_DB_ATTRIBUTE_DATA aAttributeFormatData =
987 {
988 AttributeFormat,
989 0,
990 NULL
991 };
992 CSSM_DB_ATTRIBUTE_DATA aRecordAttributes[] =
993 {
994 aRelationIDData,
995 aAttributeIDData,
996 aAttributeNameFormatData,
997 aAttributeNameData,
998 aAttributeNameIDData,
999 aAttributeFormatData
1000 };
1001 CSSM_DB_RECORD_ATTRIBUTE_DATA aRecordAttributeData =
1002 {
1003 aMetaRecord.dataRecordType(),
1004 0,
1005 sizeof(aRecordAttributes) / sizeof(CSSM_DB_ATTRIBUTE_DATA),
1006 aRecordAttributes
1007 };
1008 CssmDbRecordAttributeData &aRecordData = CssmDbRecordAttributeData::overlay(aRecordAttributeData);
1009
1010 TrackingAllocator recordAllocator(Allocator::standard());
1011 for (uint32 aRecord = 0; aRecord != aRecordsCount; aRecord++)
1012 {
1013 ReadSection aRecordSection = MetaRecord::readSection(aRecordsSection, aReadOffset);
1014 uint32 aRecordSize = aRecordSection.size();
1015 aReadOffset += aRecordSize;
1016 aMetaRecord.unpackRecord(aRecordSection, recordAllocator,
1017 &aRecordAttributeData, NULL, 0);
1018 // Create the attribute coresponding to this entry
1019 if (aRecordData[0].size() != 1 || aRecordData[0].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
1020 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1021 uint32 aRelationId = aRecordData[0];
1022
1023 // Skip the schema relations for the meta tables themselves.
1024 // FIXME: this hard-wires the meta-table relation IDs to be
1025 // within {CSSM_DB_RECORDTYPE_SCHEMA_START...
1026 // CSSM_DB_RECORDTYPE_SCHEMA_END} (which is {0..4}).
1027 // Bogus - the MDS schema relation IDs start at
1028 // CSSM_DB_RELATIONID_MDS_START which is 0x40000000.
1029 // Ref. Radar 2817921.
1030 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= aRelationId && aRelationId < CSSM_DB_RECORDTYPE_SCHEMA_END)
1031 continue;
1032
1033 // Get the MetaRecord corresponding to the specified RelationId
1034 MetaRecord &aMetaRecord = findTable(aRelationId).getMetaRecord();
1035
1036 if (aRecordData[1].size() != 1
1037 || aRecordData[1].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1038 || aRecordData[2].size() != 1
1039 || aRecordData[2].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1040 || aRecordData[5].size() != 1
1041 || aRecordData[5].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
1042 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1043
1044 uint32 anAttributeId = aRecordData[1];
1045 uint32 anAttributeNameFormat = aRecordData[2];
1046 uint32 anAttributeFormat = aRecordData[5];
1047 auto_ptr<string> aName;
1048 const CssmData *aNameID = NULL;
1049
1050 if (aRecordData[3].size() == 1)
1051 {
1052 if (aRecordData[3].format() != CSSM_DB_ATTRIBUTE_FORMAT_STRING)
1053 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1054
1055 auto_ptr<string> aName2(new string(static_cast<string>(aRecordData[3])));
1056 aName = aName2;
1057 }
1058
1059 if (aRecordData[4].size() == 1)
1060 {
1061 if (aRecordData[4].format() != CSSM_DB_ATTRIBUTE_FORMAT_BLOB)
1062 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1063
1064 // @@@ Invoking conversion operator to CssmData & on aRecordData[4]
1065 // And taking address of result.
1066 aNameID = &static_cast<const CssmData &>(aRecordData[4]);
1067 }
1068
1069 // Make sure that the attribute specified by anAttributeNameFormat is present.
1070 switch (anAttributeNameFormat)
1071 {
1072 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
1073 if (aRecordData[3].size() != 1)
1074 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1075 break;
1076 case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
1077 if (aRecordData[4].size() != 1)
1078 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1079 break;
1080 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
1081 break;
1082 default:
1083 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1084 }
1085
1086 // Create the attribute
1087 aMetaRecord.createAttribute(aName.get(), aNameID, anAttributeId, anAttributeFormat);
1088 }
1089
1090 // initialize the indexes associated with each table
1091 {
1092 TableMap::iterator it;
1093 for (it = mTableMap.begin(); it != mTableMap.end(); it++)
1094 it->second->readIndexSection();
1095 }
1096 }
1097 catch(...)
1098 {
1099 for_each_map_delete(mTableMap.begin(), mTableMap.end());
1100 mTableMap.clear();
1101 throw;
1102 }
1103 }
1104
1105 const RecordId
1106 DbVersion::getRecord(Table::Id inTableId, const RecordId &inRecordId,
1107 CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
1108 CssmData *inoutData,
1109 Allocator &inAllocator) const
1110 {
1111 return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
1112 inoutData, inAllocator);
1113 }
1114
1115 Cursor *
1116 DbVersion::createCursor(const CSSM_QUERY *inQuery) const
1117 {
1118 // XXX We should add support for these special query types
1119 // By Creating a Cursor that iterates over multiple tables
1120 if (!inQuery || inQuery->RecordType == CSSM_DL_DB_RECORD_ANY
1121 || inQuery->RecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
1122 {
1123 return new MultiCursor(inQuery, *this);
1124 }
1125
1126 return findTable(inQuery->RecordType).createCursor(inQuery, *this);
1127 }
1128
1129 bool DbVersion::hasTable(Table::Id inTableId) const
1130 {
1131 TableMap::const_iterator it = mTableMap.find(inTableId);
1132 return it != mTableMap.end();
1133 }
1134
1135 const Table &
1136 DbVersion::findTable(Table::Id inTableId) const
1137 {
1138 TableMap::const_iterator it = mTableMap.find(inTableId);
1139 if (it == mTableMap.end())
1140 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1141 return *it->second;
1142 }
1143
1144 Table &
1145 DbVersion::findTable(Table::Id inTableId)
1146 {
1147 TableMap::iterator it = mTableMap.find(inTableId);
1148 if (it == mTableMap.end())
1149 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1150 return *it->second;
1151 }
1152
1153 //
1154 // Cursor implemetation
1155 //
1156 Cursor::Cursor()
1157 {
1158 }
1159
1160 Cursor::Cursor(const DbVersion &inDbVersion) : mDbVersion(&inDbVersion)
1161 {
1162 }
1163
1164 Cursor::~Cursor()
1165 {
1166 }
1167
1168 bool
1169 Cursor::next(Table::Id &outTableId,
1170 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
1171 CssmData *outData,
1172 Allocator &inAllocator,
1173 RecordId &recordId)
1174 {
1175 return false;
1176 }
1177
1178 //
1179 // LinearCursor implemetation
1180 //
1181 LinearCursor::LinearCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion,
1182 const Table &inTable) :
1183 Cursor(inDbVersion),
1184 mRecordsCount(inTable.getRecordsCount()),
1185 mRecord(0),
1186 mRecordsSection(inTable.getRecordsSection()),
1187 mReadOffset(0),
1188 mMetaRecord(inTable.getMetaRecord())
1189 {
1190 if (inQuery)
1191 {
1192 mConjunctive = inQuery->Conjunctive;
1193 mQueryFlags = inQuery->QueryFlags;
1194 // XXX Do something with inQuery->QueryLimits?
1195 uint32 aPredicatesCount = inQuery->NumSelectionPredicates;
1196 mPredicates.resize(aPredicatesCount);
1197 try
1198 {
1199 for (uint32 anIndex = 0; anIndex < aPredicatesCount; anIndex++)
1200 {
1201 CSSM_SELECTION_PREDICATE &aPredicate = inQuery->SelectionPredicate[anIndex];
1202 mPredicates[anIndex] = new SelectionPredicate(mMetaRecord, aPredicate);
1203 }
1204 }
1205 catch(...)
1206 {
1207 for_each_delete(mPredicates.begin(), mPredicates.end());
1208 throw;
1209 }
1210 }
1211 }
1212
1213 LinearCursor::~LinearCursor()
1214 {
1215 for_each_delete(mPredicates.begin(), mPredicates.end());
1216 }
1217
1218 bool
1219 LinearCursor::next(Table::Id &outTableId,
1220 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
1221 CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
1222 {
1223 while (mRecord++ < mRecordsCount)
1224 {
1225 ReadSection aRecordSection = MetaRecord::readSection(mRecordsSection, mReadOffset);
1226 uint32 aRecordSize = aRecordSection.size();
1227 mReadOffset += aRecordSize;
1228
1229 PredicateVector::const_iterator anIt = mPredicates.begin();
1230 PredicateVector::const_iterator anEnd = mPredicates.end();
1231 bool aMatch;
1232 if (anIt == anEnd)
1233 {
1234 // If there are no predicates we have a match.
1235 aMatch = true;
1236 }
1237 else if (mConjunctive == CSSM_DB_OR)
1238 {
1239 // If mConjunctive is OR, the first predicate that returns
1240 // true indicates a match. Dropthough means no match
1241 aMatch = false;
1242 for (; anIt != anEnd; anIt++)
1243 {
1244 if ((*anIt)->evaluate(aRecordSection))
1245 {
1246 aMatch = true;
1247 break;
1248 }
1249 }
1250 }
1251 else if (mConjunctive == CSSM_DB_AND || mConjunctive == CSSM_DB_NONE)
1252 {
1253 // If mConjunctive is AND (or NONE), the first predicate that returns
1254 // false indicates a mismatch. Dropthough means a match
1255 aMatch = true;
1256 for (; anIt != anEnd; anIt++)
1257 {
1258 if (!(*anIt)->evaluate(aRecordSection))
1259 {
1260 aMatch = false;
1261 break;
1262 }
1263 }
1264 }
1265 else
1266 {
1267 // XXX Should be CSSMERR_DL_INVALID_QUERY (or CSSMERR_DL_INVALID_CONJUNTIVE).
1268 CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_QUERY);
1269 }
1270
1271 if (aMatch)
1272 {
1273 // Get the actual record.
1274 mMetaRecord.unpackRecord(aRecordSection, inAllocator,
1275 inoutAttributes, inoutData,
1276 mQueryFlags);
1277 outTableId = mMetaRecord.dataRecordType();
1278 recordId = MetaRecord::unpackRecordId(aRecordSection);
1279 return true;
1280 }
1281 }
1282
1283 return false;
1284 }
1285
1286 //
1287 // IndexCursor
1288 //
1289
1290 IndexCursor::IndexCursor(DbQueryKey *queryKey, const DbVersion &inDbVersion,
1291 const Table &table, const DbConstIndex *index) :
1292 Cursor(inDbVersion), mQueryKey(queryKey), mTable(table), mIndex(index)
1293 {
1294 index->performQuery(*queryKey, mBegin, mEnd);
1295 }
1296
1297 IndexCursor::~IndexCursor()
1298 {
1299 // the query key will be deleted automatically, since it's an auto_ptr
1300 }
1301
1302 bool
1303 IndexCursor::next(Table::Id &outTableId,
1304 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
1305 CssmData *outData,
1306 Allocator &inAllocator, RecordId &recordId)
1307 {
1308 if (mBegin == mEnd)
1309 return false;
1310
1311 ReadSection rs = mIndex->getRecordSection(mBegin++);
1312 const MetaRecord &metaRecord = mTable.getMetaRecord();
1313
1314 outTableId = metaRecord.dataRecordType();
1315 metaRecord.unpackRecord(rs, inAllocator, outAttributes, outData, 0);
1316
1317 recordId = MetaRecord::unpackRecordId(rs);
1318 return true;
1319 }
1320
1321 //
1322 // MultiCursor
1323 //
1324 MultiCursor::MultiCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion) :
1325 Cursor(inDbVersion), mTableIterator(inDbVersion.begin())
1326 {
1327 if (inQuery)
1328 mQuery.reset(new CssmAutoQuery(*inQuery));
1329 else
1330 {
1331 mQuery.reset(new CssmAutoQuery());
1332 mQuery->recordType(CSSM_DL_DB_RECORD_ANY);
1333 }
1334 }
1335
1336 MultiCursor::~MultiCursor()
1337 {
1338 }
1339
1340 bool
1341 MultiCursor::next(Table::Id &outTableId,
1342 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
1343 CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
1344 {
1345 for (;;)
1346 {
1347 if (!mCursor.get())
1348 {
1349 if (mTableIterator == mDbVersion->end())
1350 return false;
1351
1352 const Table &aTable = *mTableIterator++;
1353 if (!aTable.matchesTableId(mQuery->recordType()))
1354 continue;
1355
1356 mCursor.reset(aTable.createCursor(mQuery.get(), *mDbVersion));
1357 }
1358
1359 if (mCursor->next(outTableId, inoutAttributes, inoutData, inAllocator, recordId))
1360 return true;
1361
1362 mCursor.reset(NULL);
1363 }
1364 }
1365
1366
1367 //
1368 // DbModifier
1369 //
1370 DbModifier::DbModifier(AtomicFile &inAtomicFile, const AppleDatabase &db) :
1371 Metadata(),
1372 mDbVersion(),
1373 mAtomicFile(inAtomicFile),
1374 mDb(db)
1375 {
1376 }
1377
1378 DbModifier::~DbModifier()
1379 {
1380 try
1381 {
1382 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1383 // mAtomicTempFile will do automatic rollback on destruction.
1384 }
1385 catch(...) {}
1386 }
1387
1388 const RefPointer<const DbVersion>
1389 DbModifier::getDbVersion(bool force)
1390 {
1391 StLock<Mutex> _(mDbVersionLock);
1392
1393 /* Initialize the shared memory file change mechanism */
1394 pthread_once(&gCommonInitMutex, initCommon);
1395
1396 /* If we don't have a mDbVersion yet, or we are force to re-read the file
1397 before a write transaction, or we have received any notifications after
1398 the last time we read the file, or more than kForceReReadTime seconds
1399 have passed since the last time we read the file, we open the file and
1400 check if it has changed. */
1401 if (!mDbVersion ||
1402 force ||
1403 gSegment == NULL ||
1404 mNotifyCount != *gSegment ||
1405 CFAbsoluteTimeGetCurrent() > mDbLastRead + kForceReReadTime)
1406 {
1407 RefPointer <AtomicBufferedFile> atomicBufferedFile(mAtomicFile.read());
1408 off_t length = atomicBufferedFile->open();
1409 /* Record the number of notifications we've seen and when we last
1410 opened the file. */
1411 if (gSegment != NULL)
1412 {
1413 mNotifyCount = *gSegment;
1414 }
1415
1416 mDbLastRead = CFAbsoluteTimeGetCurrent();
1417
1418 /* If we already have a mDbVersion, let's check if we can reuse it. */
1419 if (mDbVersion)
1420 {
1421 if (length < AtomSize)
1422 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1423
1424 off_t bytesRead = 0;
1425 const uint8 *ptr = atomicBufferedFile->read(length - AtomSize,
1426 AtomSize, bytesRead);
1427 ReadSection aVersionSection(ptr, (size_t)bytesRead);
1428 uint32 aVersionId = aVersionSection[0];
1429
1430 /* If the version stamp hasn't changed the old mDbVersion is still
1431 current. */
1432 if (aVersionId == mDbVersion->getVersionId())
1433 return mDbVersion;
1434 }
1435
1436 mDbVersion = new DbVersion(mDb, atomicBufferedFile);
1437 }
1438
1439 return mDbVersion;
1440 }
1441
1442 void
1443 DbModifier::createDatabase(const CSSM_DBINFO &inDbInfo,
1444 const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry,
1445 mode_t mode)
1446 {
1447 // XXX This needs better locking. There is a possible race condition between
1448 // two concurrent creators. Or a writer/creator or a close/create etc.
1449 if (mAtomicTempFile || !mModifiedTableMap.empty())
1450 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
1451
1452 mAtomicTempFile = mAtomicFile.create(mode);
1453 // Set mVersionId to one since this is the first version of the database.
1454 mVersionId = 1;
1455
1456 // we need to create the meta tables first, because inserting tables
1457 // (including the meta tables themselves) relies on them being there
1458 createTable(new MetaRecord(mDb.schemaRelations));
1459 createTable(new MetaRecord(mDb.schemaAttributes));
1460 createTable(new MetaRecord(mDb.schemaIndexes));
1461 createTable(new MetaRecord(mDb.schemaParsingModule));
1462
1463 // now add the meta-tables' schema to the meta tables themselves
1464 insertTableSchema(mDb.schemaRelations);
1465 insertTableSchema(mDb.schemaAttributes);
1466 insertTableSchema(mDb.schemaIndexes);
1467 insertTableSchema(mDb.schemaParsingModule);
1468
1469 if (inInitialAclEntry != NULL)
1470 {
1471 //createACL(*inInitialAclEntry);
1472 }
1473
1474 if (inDbInfo.NumberOfRecordTypes == 0)
1475 return;
1476 if (inDbInfo.RecordAttributeNames == NULL)
1477 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1478 if (inDbInfo.RecordIndexes == NULL)
1479 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_INDEX);
1480 if (inDbInfo.DefaultParsingModules == NULL)
1481 CssmError::throwMe(CSSMERR_DL_INVALID_PARSING_MODULE);
1482
1483 for (uint32 anIndex = 0; anIndex < inDbInfo.NumberOfRecordTypes; anIndex++)
1484 {
1485 insertTable(CssmDbRecordAttributeInfo::overlay(inDbInfo.RecordAttributeNames[anIndex]),
1486 &inDbInfo.RecordIndexes[anIndex],
1487 &inDbInfo.DefaultParsingModules[anIndex]);
1488 }
1489 }
1490
1491 void DbModifier::openDatabase()
1492 {
1493 // No need to do anything on open if we are already writing the database.
1494 if (!mAtomicTempFile)
1495 getDbVersion(false);
1496 }
1497
1498 void DbModifier::closeDatabase()
1499 {
1500 commit(); // XXX Requires write lock.
1501 StLock<Mutex> _(mDbVersionLock);
1502 mDbVersion = NULL;
1503 }
1504
1505 void DbModifier::deleteDatabase()
1506 {
1507 bool isDirty = mAtomicTempFile;
1508 rollback(); // XXX Requires write lock.
1509 StLock<Mutex> _(mDbVersionLock);
1510
1511 // Clean up mModifiedTableMap in case this object gets reused again for
1512 // a new create.
1513 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1514 mModifiedTableMap.clear();
1515
1516 // If the database was dirty and we had no mDbVersion yet then rollback()
1517 // would have deleted the db.
1518 if (!isDirty || mDbVersion)
1519 {
1520 mDbVersion = NULL;
1521 mAtomicFile.performDelete();
1522 }
1523 }
1524
1525 void
1526 DbModifier::modifyDatabase()
1527 {
1528 if (mAtomicTempFile)
1529 return;
1530
1531 try
1532 {
1533 mAtomicTempFile = mAtomicFile.write();
1534 // Now we are holding the write lock make sure we get the latest greatest version of the db.
1535 // Also set mVersionId to one more that that of the old database.
1536 mVersionId = getDbVersion(true)->getVersionId() + 1;
1537
1538 // Never make a database with mVersionId 0 since it makes bad things happen to Jaguar and older systems
1539 if (mVersionId == 0)
1540 mVersionId = 1;
1541
1542 // Remove all old modified tables
1543 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1544 mModifiedTableMap.clear();
1545
1546 // Setup the new tables
1547 DbVersion::TableMap::const_iterator anIt =
1548 mDbVersion->mTableMap.begin();
1549 DbVersion::TableMap::const_iterator anEnd =
1550 mDbVersion->mTableMap.end();
1551 for (; anIt != anEnd; ++anIt)
1552 {
1553 auto_ptr<ModifiedTable> aTable(new ModifiedTable(anIt->second));
1554 mModifiedTableMap.insert(ModifiedTableMap::value_type(anIt->first,
1555 aTable.get()));
1556 aTable.release();
1557 }
1558 }
1559 catch(...)
1560 {
1561 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1562 mModifiedTableMap.clear();
1563 rollback();
1564 throw;
1565 }
1566 }
1567
1568 void
1569 DbModifier::deleteRecord(Table::Id inTableId, const RecordId &inRecordId)
1570 {
1571 modifyDatabase();
1572 findTable(inTableId).deleteRecord(inRecordId);
1573 }
1574
1575 const RecordId
1576 DbModifier::insertRecord(Table::Id inTableId,
1577 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
1578 const CssmData *inData)
1579 {
1580 modifyDatabase();
1581 return findTable(inTableId).insertRecord(mVersionId, inAttributes, inData);
1582 }
1583
1584 const RecordId
1585 DbModifier::updateRecord(Table::Id inTableId, const RecordId &inRecordId,
1586 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
1587 const CssmData *inData,
1588 CSSM_DB_MODIFY_MODE inModifyMode)
1589 {
1590 modifyDatabase();
1591 return findTable(inTableId).updateRecord(inRecordId, inAttributes, inData, inModifyMode);
1592 }
1593
1594 // Create a table associated with a given metarecord, and add the table
1595 // to the database.
1596
1597 ModifiedTable *
1598 DbModifier::createTable(MetaRecord *inMetaRecord)
1599 {
1600 auto_ptr<MetaRecord> aMetaRecord(inMetaRecord);
1601 auto_ptr<ModifiedTable> aModifiedTable(new ModifiedTable(inMetaRecord));
1602 // Now that aModifiedTable is fully constructed it owns inMetaRecord
1603 aMetaRecord.release();
1604
1605 if (!mModifiedTableMap.insert
1606 (ModifiedTableMap::value_type(inMetaRecord->dataRecordType(),
1607 aModifiedTable.get())).second)
1608 {
1609 // XXX Should be CSSMERR_DL_DUPLICATE_RECORDTYPE. Since that
1610 // doesn't exist we report that the metatable's unique index would
1611 // no longer be valid
1612 CssmError::throwMe(CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA);
1613 }
1614
1615 return aModifiedTable.release();
1616 }
1617
1618 void
1619 DbModifier::deleteTable(Table::Id inTableId)
1620 {
1621 modifyDatabase();
1622 // Can't delete schema tables.
1623 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= inTableId
1624 && inTableId < CSSM_DB_RECORDTYPE_SCHEMA_END)
1625 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1626
1627 // Find the ModifiedTable and delete it
1628 ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
1629 if (it == mModifiedTableMap.end())
1630 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1631
1632 delete it->second;
1633 mModifiedTableMap.erase(it);
1634 }
1635
1636 uint32
1637 DbModifier::writeAuthSection(uint32 inSectionOffset)
1638 {
1639 WriteSection anAuthSection;
1640
1641 // XXX Put real data into the authsection.
1642 uint32 anOffset = anAuthSection.put(0, 0);
1643 anAuthSection.size(anOffset);
1644
1645 mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
1646 anAuthSection.address(), anAuthSection.size());
1647 return inSectionOffset + anOffset;
1648 }
1649
1650 uint32
1651 DbModifier::writeSchemaSection(uint32 inSectionOffset)
1652 {
1653 uint32 aTableCount = (uint32) mModifiedTableMap.size();
1654 WriteSection aTableSection(Allocator::standard(),
1655 OffsetTables + AtomSize * aTableCount);
1656 // Set aTableSection to the correct size.
1657 aTableSection.size(OffsetTables + AtomSize * aTableCount);
1658 aTableSection.put(OffsetTablesCount, aTableCount);
1659
1660 uint32 anOffset = inSectionOffset + OffsetTables + AtomSize * aTableCount;
1661 ModifiedTableMap::const_iterator anIt = mModifiedTableMap.begin();
1662 ModifiedTableMap::const_iterator anEnd = mModifiedTableMap.end();
1663 for (uint32 aTableNumber = 0; anIt != anEnd; anIt++, aTableNumber++)
1664 {
1665 // Put the offset to the current table relative to the start of
1666 // this section into the tables array
1667 aTableSection.put(OffsetTables + AtomSize * aTableNumber,
1668 anOffset - inSectionOffset);
1669 anOffset = anIt->second->writeTable(*mAtomicTempFile, anOffset);
1670 }
1671
1672 aTableSection.put(OffsetSchemaSize, anOffset - inSectionOffset);
1673 mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
1674 aTableSection.address(), aTableSection.size());
1675
1676 return anOffset;
1677 }
1678
1679 void
1680 DbModifier::commit()
1681 {
1682 if (!mAtomicTempFile)
1683 return;
1684 try
1685 {
1686 WriteSection aHeaderSection(Allocator::standard(), size_t(HeaderSize));
1687 // Set aHeaderSection to the correct size.
1688 aHeaderSection.size(HeaderSize);
1689
1690 // Start writing sections after the header
1691 uint32 anOffset = HeaderOffset + HeaderSize;
1692
1693 // Write auth section
1694 aHeaderSection.put(OffsetAuthOffset, anOffset);
1695 anOffset = writeAuthSection(anOffset);
1696 // Write schema section
1697 aHeaderSection.put(OffsetSchemaOffset, anOffset);
1698 anOffset = writeSchemaSection(anOffset);
1699
1700 // Write out the file header.
1701 aHeaderSection.put(OffsetMagic, HeaderMagic);
1702 aHeaderSection.put(OffsetVersion, HeaderVersion);
1703 mAtomicTempFile->write(AtomicFile::FromStart, HeaderOffset,
1704 aHeaderSection.address(), aHeaderSection.size());
1705
1706 // Write out the versionId.
1707 WriteSection aVersionSection(Allocator::standard(), size_t(AtomSize));
1708 anOffset = aVersionSection.put(0, mVersionId);
1709 aVersionSection.size(anOffset);
1710
1711 mAtomicTempFile->write(AtomicFile::FromEnd, 0,
1712 aVersionSection.address(), aVersionSection.size());
1713
1714 mAtomicTempFile->commit();
1715 mAtomicTempFile = NULL;
1716 /* Initialize the shared memory file change mechanism */
1717 pthread_once(&gCommonInitMutex, initCommon);
1718
1719 if (gSegment != NULL)
1720 {
1721 /*
1722 PLEASE NOTE:
1723
1724 The following operation is endian safe because we are not looking
1725 for monotonic increase. I have tested every possible value of
1726 *gSegment, and there is no value for which alternating
1727 big and little endian increments will produce the original value.
1728 */
1729
1730 OSAtomicIncrement32Barrier (gSegment);
1731 }
1732 }
1733 catch(...)
1734 {
1735 rollback();
1736 throw;
1737 }
1738 }
1739
1740 void
1741 DbModifier::rollback() throw()
1742 {
1743 // This will destroy the AtomicTempFile if we have one causing it to rollback.
1744 mAtomicTempFile = NULL;
1745 }
1746
1747 const RecordId
1748 DbModifier::getRecord(Table::Id inTableId, const RecordId &inRecordId,
1749 CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
1750 CssmData *inoutData, Allocator &inAllocator)
1751 {
1752 if (mAtomicTempFile)
1753 {
1754 // We are in the midst of changing the database.
1755 return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
1756 inoutData, inAllocator);
1757 }
1758 else
1759 {
1760 return getDbVersion(false)->getRecord(inTableId, inRecordId,
1761 inoutAttributes, inoutData, inAllocator);
1762 }
1763 }
1764
1765 Cursor *
1766 DbModifier::createCursor(const CSSM_QUERY *inQuery)
1767 {
1768 if (mAtomicTempFile)
1769 {
1770 // We are modifying this database.
1771
1772 // If we have a mDbVersion already then it's a snapshot of the database
1773 // right before the modifications started. So return a cursor using
1774 // that.
1775 if (mDbVersion)
1776 return mDbVersion->createCursor(inQuery);
1777
1778 // This is a newly created but never commited database. Return a
1779 // Cursor that will not return any matches.
1780 return new Cursor();
1781 }
1782
1783 // Get the latest and greatest version of the db and create the cursor
1784 // on that.
1785 return getDbVersion(false)->createCursor(inQuery);
1786 }
1787
1788 // Insert schema records for a new table into the metatables of the database. This gets
1789 // called while a database is being created.
1790
1791 void
1792 DbModifier::insertTableSchema(const CssmDbRecordAttributeInfo &inInfo,
1793 const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */)
1794 {
1795 ModifiedTable &aTable = findTable(inInfo.DataRecordType);
1796 const MetaRecord &aMetaRecord = aTable.getMetaRecord();
1797
1798 CssmAutoDbRecordAttributeData aRecordBuilder(5); // Set capacity to 5 so we don't need to grow
1799
1800 // Create the entry for the SchemaRelations table.
1801 aRecordBuilder.add(RelationID, inInfo.recordType());
1802 aRecordBuilder.add(RelationName, mDb.recordName(inInfo.recordType()));
1803
1804 // Insert the record into the SchemaRelations ModifiedTable
1805 findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
1806 &aRecordBuilder, NULL);
1807
1808 ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
1809 for (uint32 anIndex = 0; anIndex < inInfo.size(); anIndex++)
1810 {
1811 // Create an entry for the SchemaAttributes table.
1812 aRecordBuilder.clear();
1813 aRecordBuilder.add(RelationID, inInfo.recordType());
1814 aRecordBuilder.add(AttributeNameFormat, inInfo.at(anIndex).nameFormat());
1815
1816 uint32 attributeId = aMetaRecord.metaAttribute(inInfo.at(anIndex)).attributeId();
1817
1818 switch (inInfo.at(anIndex).nameFormat())
1819 {
1820 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
1821 aRecordBuilder.add(AttributeName, inInfo.at(anIndex).Label.AttributeName);
1822 break;
1823 case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
1824 aRecordBuilder.add(AttributeNameID, inInfo.at(anIndex).Label.AttributeOID);
1825 break;
1826 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
1827 break;
1828 default:
1829 CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
1830 }
1831
1832 aRecordBuilder.add(AttributeID, attributeId);
1833 aRecordBuilder.add(AttributeFormat, inInfo.at(anIndex).format());
1834
1835 // Insert the record into the SchemaAttributes ModifiedTable
1836 anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1837 }
1838
1839 if (inIndexInfo != NULL) {
1840
1841 if (inIndexInfo->DataRecordType != inInfo.DataRecordType &&
1842 inIndexInfo->NumberOfIndexes > 0)
1843 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1844
1845 ModifiedTable &indexMetaTable = findTable(mDb.schemaIndexes.DataRecordType);
1846 uint32 aNumberOfIndexes = inIndexInfo->NumberOfIndexes;
1847
1848 for (uint32 anIndex = 0; anIndex < aNumberOfIndexes; anIndex++)
1849 {
1850 const CssmDbIndexInfo &thisIndex = CssmDbIndexInfo::overlay(inIndexInfo->IndexInfo[anIndex]);
1851
1852 // make sure the index is supported
1853 if (thisIndex.dataLocation() != CSSM_DB_INDEX_ON_ATTRIBUTE)
1854 CssmError::throwMe(CSSMERR_DL_INVALID_INDEX_INFO);
1855
1856 // assign an index ID: the unique index is ID 0, all others are ID > 0
1857 uint32 indexId;
1858 if (thisIndex.IndexType == CSSM_DB_INDEX_UNIQUE)
1859 indexId = 0;
1860 else
1861 indexId = anIndex + 1;
1862
1863 // figure out the attribute ID
1864 uint32 attributeId =
1865 aMetaRecord.metaAttribute(thisIndex.Info).attributeId();
1866
1867 // Create an entry for the SchemaIndexes table.
1868 aRecordBuilder.clear();
1869 aRecordBuilder.add(RelationID, inInfo.DataRecordType);
1870 aRecordBuilder.add(IndexID, indexId);
1871 aRecordBuilder.add(AttributeID, attributeId);
1872 aRecordBuilder.add(IndexType, thisIndex.IndexType);
1873 aRecordBuilder.add(IndexedDataLocation, thisIndex.IndexedDataLocation);
1874
1875 // Insert the record into the SchemaIndexes ModifiedTable
1876 indexMetaTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1877
1878 // update the table's index objects
1879 DbMutableIndex &index = aTable.findIndex(indexId, aMetaRecord, indexId == 0);
1880 index.appendAttribute(attributeId);
1881 }
1882 }
1883 }
1884
1885 // Insert a new table. The attribute info is required; the index and parsing module
1886 // descriptions are optional. This version gets called during the creation of a
1887 // database.
1888
1889 void
1890 DbModifier::insertTable(const CssmDbRecordAttributeInfo &inInfo,
1891 const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */,
1892 const CSSM_DB_PARSING_MODULE_INFO *inParsingModule /* = NULL */)
1893 {
1894 modifyDatabase();
1895 createTable(new MetaRecord(inInfo));
1896 insertTableSchema(inInfo, inIndexInfo);
1897 }
1898
1899 // Insert a new table. This is the version that gets called when a table is added
1900 // after a database has been created.
1901
1902 void
1903 DbModifier::insertTable(Table::Id inTableId, const string &inTableName,
1904 uint32 inNumberOfAttributes,
1905 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
1906 uint32 inNumberOfIndexes,
1907 const CSSM_DB_SCHEMA_INDEX_INFO *inIndexInfo)
1908 {
1909 modifyDatabase();
1910 ModifiedTable *aTable = createTable(new MetaRecord(inTableId, inNumberOfAttributes, inAttributeInfo));
1911
1912 CssmAutoDbRecordAttributeData aRecordBuilder(6); // Set capacity to 6 so we don't need to grow
1913
1914 // Create the entry for the SchemaRelations table.
1915 aRecordBuilder.add(RelationID, inTableId);
1916 aRecordBuilder.add(RelationName, inTableName);
1917
1918 // Insert the record into the SchemaRelations ModifiedTable
1919 findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
1920 &aRecordBuilder, NULL);
1921
1922 ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
1923 for (uint32 anIndex = 0; anIndex < inNumberOfAttributes; anIndex++)
1924 {
1925 // Create an entry for the SchemaAttributes table.
1926 aRecordBuilder.clear();
1927 aRecordBuilder.add(RelationID, inTableId);
1928 // XXX What should this be? We set it to CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER for now
1929 // since the AttributeID is always valid.
1930 aRecordBuilder.add(AttributeNameFormat, uint32(CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER));
1931 aRecordBuilder.add(AttributeID, inAttributeInfo[anIndex].AttributeId);
1932 if (inAttributeInfo[anIndex].AttributeName)
1933 aRecordBuilder.add(AttributeName, inAttributeInfo[anIndex].AttributeName);
1934 if (inAttributeInfo[anIndex].AttributeNameID.Length > 0)
1935 aRecordBuilder.add(AttributeNameID, inAttributeInfo[anIndex].AttributeNameID);
1936 aRecordBuilder.add(AttributeFormat, inAttributeInfo[anIndex].DataType);
1937
1938 // Insert the record into the SchemaAttributes ModifiedTable
1939 anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1940 }
1941
1942 ModifiedTable &anIndexTable = findTable(mDb.schemaIndexes.DataRecordType);
1943 for (uint32 anIndex = 0; anIndex < inNumberOfIndexes; anIndex++)
1944 {
1945 // Create an entry for the SchemaIndexes table.
1946 aRecordBuilder.clear();
1947 aRecordBuilder.add(RelationID, inTableId);
1948 aRecordBuilder.add(IndexID, inIndexInfo[anIndex].IndexId);
1949 aRecordBuilder.add(AttributeID, inIndexInfo[anIndex].AttributeId);
1950 aRecordBuilder.add(IndexType, inIndexInfo[anIndex].IndexType);
1951 aRecordBuilder.add(IndexedDataLocation, inIndexInfo[anIndex].IndexedDataLocation);
1952
1953 // Insert the record into the SchemaIndexes ModifiedTable
1954 anIndexTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1955
1956 // update the table's index objects
1957 DbMutableIndex &index = aTable->findIndex(inIndexInfo[anIndex].IndexId,
1958 aTable->getMetaRecord(), inIndexInfo[anIndex].IndexType == CSSM_DB_INDEX_UNIQUE);
1959 index.appendAttribute(inIndexInfo[anIndex].AttributeId);
1960 }
1961 }
1962
1963
1964
1965 bool DbModifier::hasTable(Table::Id inTableId)
1966 {
1967 return getDbVersion(false)->hasTable(inTableId);
1968 }
1969
1970
1971
1972 ModifiedTable &
1973 DbModifier::findTable(Table::Id inTableId)
1974 {
1975 ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
1976 if (it == mModifiedTableMap.end())
1977 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1978 return *it->second;
1979 }
1980
1981
1982 //
1983 // AppleDatabaseManager implementation
1984 //
1985
1986 AppleDatabaseManager::AppleDatabaseManager(const AppleDatabaseTableName *tableNames)
1987 : DatabaseManager(),
1988 mTableNames(tableNames)
1989 {
1990 // make sure that a proper set of table ids and names has been provided
1991
1992 if (!mTableNames)
1993 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
1994 else {
1995 uint32 i;
1996 for (i = 0; mTableNames[i].mTableName; i++) {}
1997 if (i < AppleDatabaseTableName::kNumRequiredTableNames)
1998 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
1999 }
2000 }
2001
2002 Database *
2003 AppleDatabaseManager::make(const DbName &inDbName)
2004 {
2005 return new AppleDatabase(inDbName, mTableNames);
2006 }
2007
2008
2009 //
2010 // AppleDbContext implementation
2011 //
2012
2013 /* This is the version 0 CSSM_APPLEDL_OPEN_PARAMETERS struct used up to 10.2.x. */
2014 extern "C" {
2015
2016 typedef struct cssm_appledl_open_parameters_v0
2017 {
2018 uint32 length; /* Should be sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0). */
2019 uint32 version; /* Should be 0. */
2020 CSSM_BOOL autoCommit;
2021 } CSSM_APPLEDL_OPEN_PARAMETERS_V0;
2022
2023 };
2024
2025 AppleDbContext::AppleDbContext(Database &inDatabase,
2026 DatabaseSession &inDatabaseSession,
2027 CSSM_DB_ACCESS_TYPE inAccessRequest,
2028 const AccessCredentials *inAccessCred,
2029 const void *inOpenParameters) :
2030 DbContext(inDatabase, inDatabaseSession, inAccessRequest, inAccessCred),
2031 mAutoCommit(true),
2032 mMode(0666)
2033 {
2034 const CSSM_APPLEDL_OPEN_PARAMETERS *anOpenParameters =
2035 reinterpret_cast<const CSSM_APPLEDL_OPEN_PARAMETERS *>(inOpenParameters);
2036
2037 if (anOpenParameters)
2038 {
2039 switch (anOpenParameters->version)
2040 {
2041 case 1:
2042 if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS))
2043 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2044
2045 if (anOpenParameters->mask & kCSSM_APPLEDL_MASK_MODE)
2046 mMode = anOpenParameters->mode;
2047 /*DROPTHROUGH*/
2048 case 0:
2049 if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0))
2050 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2051
2052 mAutoCommit = anOpenParameters->autoCommit == CSSM_FALSE ? false : true;
2053 break;
2054
2055 default:
2056 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2057 }
2058 }
2059 }
2060
2061 AppleDbContext::~AppleDbContext()
2062 {
2063 }
2064
2065 //
2066 // AppleDatabase implementation
2067 //
2068 AppleDatabase::AppleDatabase(const DbName &inDbName, const AppleDatabaseTableName *tableNames) :
2069 Database(inDbName),
2070 schemaRelations(tableNames[AppleDatabaseTableName::kSchemaInfo].mTableId,
2071 sizeof(AttrSchemaRelations) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2072 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaRelations)),
2073 schemaAttributes(tableNames[AppleDatabaseTableName::kSchemaAttributes].mTableId,
2074 sizeof(AttrSchemaAttributes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2075 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaAttributes)),
2076 schemaIndexes(tableNames[AppleDatabaseTableName::kSchemaIndexes].mTableId,
2077 sizeof(AttrSchemaIndexes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2078 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaIndexes)),
2079 schemaParsingModule(tableNames[AppleDatabaseTableName::kSchemaParsingModule].mTableId,
2080 sizeof(AttrSchemaParsingModule) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2081 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaParsingModule)),
2082 mAtomicFile(mDbName.dbName()),
2083 mDbModifier(mAtomicFile, *this),
2084 mTableNames(tableNames)
2085 {
2086 /* temp check for X509Anchors access - this should removed before Leopard GM */
2087 if(!strcmp(inDbName.dbName(), "/System/Library/Keychains/X509Anchors")) {
2088 Syslog::alert("Warning: accessing obsolete X509Anchors.");
2089 }
2090 }
2091
2092 AppleDatabase::~AppleDatabase()
2093 {
2094 }
2095
2096 // Return the name of a record type. This uses a table that maps record types
2097 // to record names. The table is provided when the database is created.
2098
2099 const char *AppleDatabase::recordName(CSSM_DB_RECORDTYPE inRecordType) const
2100 {
2101 if (inRecordType == CSSM_DL_DB_RECORD_ANY || inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
2102 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
2103
2104 for (uint32 i = 0; mTableNames[i].mTableName; i++)
2105 if (mTableNames[i].mTableId == inRecordType)
2106 return mTableNames[i].mTableName;
2107
2108 return "";
2109 }
2110
2111 DbContext *
2112 AppleDatabase::makeDbContext(DatabaseSession &inDatabaseSession,
2113 CSSM_DB_ACCESS_TYPE inAccessRequest,
2114 const AccessCredentials *inAccessCred,
2115 const void *inOpenParameters)
2116 {
2117 return new AppleDbContext(*this, inDatabaseSession, inAccessRequest,
2118 inAccessCred, inOpenParameters);
2119 }
2120
2121 void
2122 AppleDatabase::dbCreate(DbContext &inDbContext, const CSSM_DBINFO &inDBInfo,
2123 const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry)
2124 {
2125 AppleDbContext &context = safer_cast<AppleDbContext &>(inDbContext);
2126 try
2127 {
2128 StLock<Mutex> _(mWriteLock);
2129 mDbModifier.createDatabase(inDBInfo, inInitialAclEntry, context.mode());
2130 }
2131 catch(...)
2132 {
2133 mDbModifier.rollback();
2134 throw;
2135 }
2136 if (context.autoCommit())
2137 mDbModifier.commit();
2138 }
2139
2140 void
2141 AppleDatabase::dbOpen(DbContext &inDbContext)
2142 {
2143 mDbModifier.openDatabase();
2144 }
2145
2146 void
2147 AppleDatabase::dbClose()
2148 {
2149 StLock<Mutex> _(mWriteLock);
2150 mDbModifier.closeDatabase();
2151 }
2152
2153 void
2154 AppleDatabase::dbDelete(DatabaseSession &inDatabaseSession,
2155 const AccessCredentials *inAccessCred)
2156 {
2157 StLock<Mutex> _(mWriteLock);
2158 // XXX Check callers credentials.
2159 mDbModifier.deleteDatabase();
2160 }
2161
2162 void
2163 AppleDatabase::createRelation(DbContext &inDbContext,
2164 CSSM_DB_RECORDTYPE inRelationID,
2165 const char *inRelationName,
2166 uint32 inNumberOfAttributes,
2167 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
2168 uint32 inNumberOfIndexes,
2169 const CSSM_DB_SCHEMA_INDEX_INFO &inIndexInfo)
2170 {
2171 try
2172 {
2173 StLock<Mutex> _(mWriteLock);
2174 // XXX Fix the refs here.
2175 mDbModifier.insertTable(inRelationID, inRelationName,
2176 inNumberOfAttributes, inAttributeInfo,
2177 inNumberOfIndexes, &inIndexInfo);
2178 }
2179 catch(...)
2180 {
2181 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2182 mDbModifier.rollback();
2183 throw;
2184 }
2185 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2186 mDbModifier.commit();
2187 }
2188
2189 void
2190 AppleDatabase::destroyRelation(DbContext &inDbContext,
2191 CSSM_DB_RECORDTYPE inRelationID)
2192 {
2193 try
2194 {
2195 StLock<Mutex> _(mWriteLock);
2196 mDbModifier.deleteTable(inRelationID);
2197 }
2198 catch(...)
2199 {
2200 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2201 mDbModifier.rollback();
2202 throw;
2203 }
2204 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2205 mDbModifier.commit();
2206 }
2207
2208 void
2209 AppleDatabase::authenticate(DbContext &inDbContext,
2210 CSSM_DB_ACCESS_TYPE inAccessRequest,
2211 const AccessCredentials &inAccessCred)
2212 {
2213 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2214 }
2215
2216 void
2217 AppleDatabase::getDbAcl(DbContext &inDbContext,
2218 const CSSM_STRING *inSelectionTag,
2219 uint32 &outNumberOfAclInfos,
2220 CSSM_ACL_ENTRY_INFO_PTR &outAclInfos)
2221 {
2222 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2223 }
2224
2225 void
2226 AppleDatabase::changeDbAcl(DbContext &inDbContext,
2227 const AccessCredentials &inAccessCred,
2228 const CSSM_ACL_EDIT &inAclEdit)
2229 {
2230 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2231 }
2232
2233 void
2234 AppleDatabase::getDbOwner(DbContext &inDbContext,
2235 CSSM_ACL_OWNER_PROTOTYPE &outOwner)
2236 {
2237 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2238 }
2239
2240 void
2241 AppleDatabase::changeDbOwner(DbContext &inDbContext,
2242 const AccessCredentials &inAccessCred,
2243 const CSSM_ACL_OWNER_PROTOTYPE &inNewOwner)
2244 {
2245 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2246 }
2247
2248 char *
2249 AppleDatabase::getDbNameFromHandle(const DbContext &inDbContext) const
2250 {
2251 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2252 }
2253
2254 CSSM_DB_UNIQUE_RECORD_PTR
2255 AppleDatabase::dataInsert(DbContext &inDbContext,
2256 CSSM_DB_RECORDTYPE inRecordType,
2257 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
2258 const CssmData *inData)
2259 {
2260 CSSM_DB_UNIQUE_RECORD_PTR anUniqueRecordPtr = NULL;
2261 try
2262 {
2263 StLock<Mutex> _(mWriteLock);
2264 const RecordId aRecordId =
2265 mDbModifier.insertRecord(inRecordType, inAttributes, inData);
2266
2267 anUniqueRecordPtr = createUniqueRecord(inDbContext, inRecordType,
2268 aRecordId);
2269 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2270 mDbModifier.commit();
2271 }
2272 catch(...)
2273 {
2274 if (anUniqueRecordPtr != NULL)
2275 freeUniqueRecord(inDbContext, *anUniqueRecordPtr);
2276
2277 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2278 mDbModifier.rollback();
2279 throw;
2280 }
2281
2282 return anUniqueRecordPtr;
2283 }
2284
2285 void
2286 AppleDatabase::dataDelete(DbContext &inDbContext,
2287 const CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
2288 {
2289 try
2290 {
2291 // syslog if it's the .Mac password
2292 CSSM_DB_RECORD_ATTRIBUTE_DATA attrData;
2293 // 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
2294 // an exception.
2295 memset(&attrData, 0, sizeof(attrData));
2296 dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
2297
2298 if (attrData.DataRecordType == CSSM_DL_DB_RECORD_GENERIC_PASSWORD)
2299 {
2300 CSSM_DB_ATTRIBUTE_DATA attributes;
2301
2302 // setup some attributes and see if we are indeed the .Mac password
2303 attributes.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
2304 attributes.Info.Label.AttributeID = 'svce';
2305 attributes.Info.AttributeFormat = 0;
2306 attributes.NumberOfValues = 1;
2307 attributes.Value = NULL;
2308
2309 attrData.NumberOfAttributes = 1;
2310 attrData.AttributeData = &attributes;
2311
2312 dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
2313
2314 // now check the results
2315 std::string dataString((const char*) attrData.AttributeData[0].Value[0].Data, attrData.AttributeData[0].Value[0].Length);
2316 if (dataString == "iTools")
2317 {
2318 syslog(LOG_WARNING, "Warning: Removed .Me password");
2319 }
2320
2321 free(attrData.AttributeData[0].Value[0].Data);
2322 free(attrData.AttributeData[0].Value);
2323 }
2324
2325 StLock<Mutex> _(mWriteLock);
2326 Table::Id aTableId;
2327 const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
2328 mDbModifier.deleteRecord(aTableId, aRecordId);
2329 }
2330 catch(...)
2331 {
2332 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2333 mDbModifier.rollback();
2334 throw;
2335 }
2336
2337 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2338 mDbModifier.commit();
2339 }
2340
2341 void
2342 AppleDatabase::dataModify(DbContext &inDbContext,
2343 CSSM_DB_RECORDTYPE inRecordType,
2344 CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord,
2345 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributesToBeModified,
2346 const CssmData *inDataToBeModified,
2347 CSSM_DB_MODIFY_MODE inModifyMode)
2348 {
2349 try
2350 {
2351 StLock<Mutex> _(mWriteLock);
2352 Table::Id aTableId;
2353 const RecordId oldRecordId = parseUniqueRecord(inoutUniqueRecord,
2354 aTableId);
2355 #if 1
2356 if (inRecordType != aTableId)
2357 #else
2358 if (inRecordType != aTableId &&
2359 inRecordType != CSSM_DL_DB_RECORD_ANY &&
2360 !(inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS &&
2361 (aTableId == CSSM_DL_DB_RECORD_PUBLIC_KEY ||
2362 aTableId == CSSM_DL_DB_RECORD_PRIVATE_KEY ||
2363 aTableId == CSSM_DL_DB_RECORD_SYMMETRIC_KEY)))
2364 #endif
2365 {
2366 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
2367 }
2368
2369 const RecordId newRecordId =
2370 mDbModifier.updateRecord(aTableId,
2371 oldRecordId,
2372 inAttributesToBeModified,
2373 inDataToBeModified,
2374 inModifyMode);
2375 updateUniqueRecord(inDbContext, aTableId, newRecordId,
2376 inoutUniqueRecord);
2377 }
2378 catch(...)
2379 {
2380 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2381 mDbModifier.rollback();
2382 throw;
2383 }
2384
2385 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2386 mDbModifier.commit();
2387 }
2388
2389 CSSM_HANDLE
2390 AppleDatabase::dataGetFirst(DbContext &inDbContext,
2391 const CssmQuery *inQuery,
2392 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2393 CssmData *inoutData,
2394 CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
2395 {
2396 // XXX: register Cursor with DbContext and have DbContext call
2397 // dataAbortQuery for all outstanding Query objects on close.
2398 auto_ptr<Cursor> aCursor(mDbModifier.createCursor(inQuery));
2399 Table::Id aTableId;
2400 RecordId aRecordId;
2401
2402 if (!aCursor->next(aTableId, inoutAttributes, inoutData,
2403 inDbContext.mDatabaseSession, aRecordId))
2404 // return a NULL handle, and implicitly delete the cursor
2405 return CSSM_INVALID_HANDLE;
2406
2407 outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);
2408 return aCursor.release()->handle(); // We didn't throw so keep the Cursor around.
2409 }
2410
2411 bool
2412 AppleDatabase::dataGetNext(DbContext &inDbContext,
2413 CSSM_HANDLE inResultsHandle,
2414 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2415 CssmData *inoutData,
2416 CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
2417 {
2418 auto_ptr<Cursor> aCursor(&HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE));
2419 Table::Id aTableId;
2420 RecordId aRecordId;
2421
2422 if (!aCursor->next(aTableId, inoutAttributes, inoutData, inDbContext.mDatabaseSession, aRecordId))
2423 return false;
2424
2425 outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);
2426
2427 aCursor.release();
2428 return true;
2429 }
2430
2431 void
2432 AppleDatabase::dataAbortQuery(DbContext &inDbContext,
2433 CSSM_HANDLE inResultsHandle)
2434 {
2435 delete &HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE);
2436 }
2437
2438 void
2439 AppleDatabase::dataGetFromUniqueRecordId(DbContext &inDbContext,
2440 const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
2441 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2442 CssmData *inoutData)
2443 {
2444 Table::Id aTableId;
2445 const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
2446 // XXX Change CDSA spec to use new RecordId returned by this function
2447 mDbModifier.getRecord(aTableId, aRecordId, inoutAttributes, inoutData,
2448 inDbContext.mDatabaseSession);
2449 }
2450
2451 void
2452 AppleDatabase::freeUniqueRecord(DbContext &inDbContext,
2453 CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
2454 {
2455 if (inUniqueRecord.RecordIdentifier.Length != 0
2456 && inUniqueRecord.RecordIdentifier.Data != NULL)
2457 {
2458 inUniqueRecord.RecordIdentifier.Length = 0;
2459 inDbContext.mDatabaseSession.free(inUniqueRecord.RecordIdentifier.Data);
2460 }
2461 inDbContext.mDatabaseSession.free(&inUniqueRecord);
2462 }
2463
2464 void
2465 AppleDatabase::updateUniqueRecord(DbContext &inDbContext,
2466 CSSM_DB_RECORDTYPE inTableId,
2467 const RecordId &inRecordId,
2468 CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord)
2469 {
2470 uint32 *aBuffer = reinterpret_cast<uint32 *>(inoutUniqueRecord.RecordIdentifier.Data);
2471 aBuffer[0] = inTableId;
2472 aBuffer[1] = inRecordId.mRecordNumber;
2473 aBuffer[2] = inRecordId.mCreateVersion;
2474 aBuffer[3] = inRecordId.mRecordVersion;
2475 }
2476
2477 CSSM_DB_UNIQUE_RECORD_PTR
2478 AppleDatabase::createUniqueRecord(DbContext &inDbContext,
2479 CSSM_DB_RECORDTYPE inTableId,
2480 const RecordId &inRecordId)
2481 {
2482 CSSM_DB_UNIQUE_RECORD_PTR aUniqueRecord =
2483 inDbContext.mDatabaseSession.alloc<CSSM_DB_UNIQUE_RECORD>();
2484 memset(aUniqueRecord, 0, sizeof(*aUniqueRecord));
2485 aUniqueRecord->RecordIdentifier.Length = sizeof(uint32) * 4;
2486 try
2487 {
2488 aUniqueRecord->RecordIdentifier.Data =
2489 inDbContext.mDatabaseSession.alloc<uint8>(sizeof(uint32) * 4);
2490 updateUniqueRecord(inDbContext, inTableId, inRecordId, *aUniqueRecord);
2491 }
2492 catch(...)
2493 {
2494 inDbContext.mDatabaseSession.free(aUniqueRecord);
2495 throw;
2496 }
2497
2498 return aUniqueRecord;
2499 }
2500
2501 const RecordId
2502 AppleDatabase::parseUniqueRecord(const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
2503 CSSM_DB_RECORDTYPE &outTableId)
2504 {
2505 if (inUniqueRecord.RecordIdentifier.Length != sizeof(uint32) * 4)
2506 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
2507
2508 uint32 *aBuffer = reinterpret_cast<uint32 *>(inUniqueRecord.RecordIdentifier.Data);
2509 outTableId = aBuffer[0];
2510 return RecordId(aBuffer[1], aBuffer[2], aBuffer[3]);
2511 }
2512
2513 void
2514 AppleDatabase::passThrough(DbContext &dbContext,
2515 uint32 passThroughId,
2516 const void *inputParams,
2517 void **outputParams)
2518 {
2519 switch (passThroughId)
2520 {
2521 case CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT:
2522 {
2523 AppleDbContext &dbc = safer_cast<AppleDbContext &>(dbContext);
2524 // Return the old state of the autoCommit flag if requested
2525 if (outputParams)
2526 *reinterpret_cast<CSSM_BOOL *>(outputParams) = dbc.autoCommit();
2527 dbc.autoCommit(inputParams ? CSSM_TRUE : CSSM_FALSE);
2528 }
2529 break;
2530
2531 case CSSM_APPLEFILEDL_COMMIT:
2532 mDbModifier.commit();
2533 break;
2534
2535 case CSSM_APPLEFILEDL_ROLLBACK:
2536 mDbModifier.rollback();
2537 break;
2538
2539 case CSSM_APPLECSPDL_DB_RELATION_EXISTS:
2540 {
2541 CSSM_BOOL returnValue;
2542
2543 CSSM_DB_RECORDTYPE recordType = *(CSSM_DB_RECORDTYPE*) inputParams;
2544 if (recordType == CSSM_DL_DB_RECORD_ANY || recordType == CSSM_DL_DB_RECORD_ALL_KEYS)
2545 {
2546 returnValue = CSSM_TRUE;
2547 }
2548 else
2549 {
2550 returnValue = mDbModifier.hasTable(recordType);
2551 }
2552
2553 *(CSSM_BOOL*) outputParams = returnValue;
2554 break;
2555 }
2556
2557 default:
2558 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2559 break;
2560 }
2561 }