]> git.saurik.com Git - apple/security.git/blob - OSX/include/security_filedb/AppleDatabase.cpp
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / include / security_filedb / AppleDatabase.cpp
1 /*
2 * Copyright (c) 2000-2001,2003,2011-2014 Apple 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 #pragma clang diagnostic push
723 #pragma clang diagnostic ignored "-Wunused-const-variable"
724
725 //
726 // Metadata
727 //
728
729 // Attribute definitions
730
731 static const CSSM_DB_ATTRIBUTE_INFO RelationID =
732 {
733 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
734 {(char*) "RelationID"},
735 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
736 };
737 static const CSSM_DB_ATTRIBUTE_INFO RelationName =
738 {
739 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
740 {(char*) "RelationName"},
741 CSSM_DB_ATTRIBUTE_FORMAT_STRING
742 };
743 static const CSSM_DB_ATTRIBUTE_INFO AttributeID =
744 {
745 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
746 {(char*) "AttributeID"},
747 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
748 };
749 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameFormat =
750 {
751 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
752 {(char*) "AttributeNameFormat"},
753 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
754 };
755 static const CSSM_DB_ATTRIBUTE_INFO AttributeName =
756 {
757 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
758 {(char*) "AttributeName"},
759 CSSM_DB_ATTRIBUTE_FORMAT_STRING
760 };
761 static const CSSM_DB_ATTRIBUTE_INFO AttributeNameID =
762 {
763 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
764 {(char*) "AttributeNameID"},
765 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
766 };
767 static const CSSM_DB_ATTRIBUTE_INFO AttributeFormat =
768 {
769 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
770 {(char*) "AttributeFormat"},
771 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
772 };
773 static const CSSM_DB_ATTRIBUTE_INFO IndexID =
774 {
775 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
776 {(char*) "IndexID"},
777 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
778 };
779 static const CSSM_DB_ATTRIBUTE_INFO IndexType =
780 {
781 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
782 {(char*) "IndexType"},
783 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
784 };
785 static const CSSM_DB_ATTRIBUTE_INFO IndexedDataLocation =
786 {
787 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
788 {(char*) "IndexedDataLocation"},
789 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
790 };
791 static const CSSM_DB_ATTRIBUTE_INFO ModuleID =
792 {
793 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
794 {(char*) "ModuleID"},
795 CSSM_DB_ATTRIBUTE_FORMAT_BLOB
796 };
797 static const CSSM_DB_ATTRIBUTE_INFO AddinVersion =
798 {
799 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
800 {(char*) "AddinVersion"},
801 CSSM_DB_ATTRIBUTE_FORMAT_STRING
802 };
803 static const CSSM_DB_ATTRIBUTE_INFO SSID =
804 {
805 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
806 {(char*) "SSID"},
807 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
808 };
809 static const CSSM_DB_ATTRIBUTE_INFO SubserviceType =
810 {
811 CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
812 {(char*) "SubserviceType"},
813 CSSM_DB_ATTRIBUTE_FORMAT_UINT32
814 };
815
816 #define ATTRIBUTE(type, name) \
817 { CSSM_DB_ATTRIBUTE_NAME_AS_STRING, { (char*) #name }, CSSM_DB_ATTRIBUTE_FORMAT_ ## type }
818
819 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaRelations[] =
820 {
821 //RelationID, RelationName
822 ATTRIBUTE(UINT32, RelationID),
823 ATTRIBUTE(STRING, RelationName)
824 };
825
826 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaAttributes[] =
827 {
828 //RelationID, AttributeID,
829 //AttributeNameFormat, AttributeName, AttributeNameID,
830 //AttributeFormat
831 ATTRIBUTE(UINT32, RelationID),
832 ATTRIBUTE(UINT32, AttributeID),
833 ATTRIBUTE(UINT32, AttributeNameFormat),
834 ATTRIBUTE(STRING, AttributeName),
835 ATTRIBUTE(BLOB, AttributeNameID),
836 ATTRIBUTE(UINT32, AttributeFormat)
837 };
838
839 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaIndexes[] =
840 {
841 ATTRIBUTE(UINT32, RelationID),
842 ATTRIBUTE(UINT32, IndexID),
843 ATTRIBUTE(UINT32, AttributeID),
844 ATTRIBUTE(UINT32, IndexType),
845 ATTRIBUTE(UINT32, IndexedDataLocation)
846 //RelationID, IndexID, AttributeID,
847 //IndexType, IndexedDataLocation
848 };
849
850 static const CSSM_DB_ATTRIBUTE_INFO AttrSchemaParsingModule[] =
851 {
852 ATTRIBUTE(UINT32, RelationID),
853 ATTRIBUTE(UINT32, AttributeID),
854 ATTRIBUTE(BLOB, ModuleID),
855 ATTRIBUTE(STRING, AddinVersion),
856 ATTRIBUTE(UINT32, SSID),
857 ATTRIBUTE(UINT32, SubserviceType)
858 //RelationID, AttributeID,
859 //ModuleID, AddinVersion, SSID, SubserviceType
860 };
861
862 #undef ATTRIBUTE
863 #pragma clang diagnostic pop
864
865 //
866 // DbVersion
867 //
868 DbVersion::DbVersion(const AppleDatabase &db, const RefPointer <AtomicBufferedFile> &inAtomicBufferedFile) :
869 mDatabase(reinterpret_cast<const uint8 *>(NULL), 0),
870 mDb(db),
871 mBufferedFile(inAtomicBufferedFile)
872 {
873 off_t aLength = mBufferedFile->length();
874 off_t bytesRead = 0;
875 const uint8 *ptr = mBufferedFile->read(0, aLength, bytesRead);
876 mBufferedFile->close();
877 mDatabase = ReadSection(ptr, (size_t)bytesRead);
878 open();
879 }
880
881 DbVersion::~DbVersion()
882 {
883 try
884 {
885 for_each_map_delete(mTableMap.begin(), mTableMap.end());
886 }
887 catch(...) {}
888 }
889
890 void
891 DbVersion::open()
892 {
893 try
894 {
895 // This is the oposite of DbModifier::commit()
896 mVersionId = mDatabase[mDatabase.size() - AtomSize];
897
898 const ReadSection aHeaderSection = mDatabase.subsection(HeaderOffset,
899 HeaderSize);
900 if (aHeaderSection.at(OffsetMagic) != HeaderMagic)
901 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
902
903 // We currently only support one version. If we support additional
904 // file format versions in the future fix this.
905 uint32 aVersion = aHeaderSection.at(OffsetVersion);
906 if (aVersion != HeaderVersion)
907 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
908
909 //const ReadSection anAuthSection =
910 // mDatabase.subsection(HeaderOffset + aHeaderSection.at(OffsetAuthOffset));
911 // XXX Do something with anAuthSection.
912
913 uint32 aSchemaOffset = aHeaderSection.at(OffsetSchemaOffset);
914 const ReadSection aSchemaSection =
915 mDatabase.subsection(HeaderOffset + aSchemaOffset);
916
917 uint32 aSchemaSize = aSchemaSection[OffsetSchemaSize];
918 // Make sure that the given range exists.
919 aSchemaSection.subsection(0, aSchemaSize);
920 uint32 aTableCount = aSchemaSection[OffsetTablesCount];
921
922 // Assert that the size of this section is big enough.
923 if (aSchemaSize < OffsetTables + AtomSize * aTableCount)
924 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
925
926 for (uint32 aTableNumber = 0; aTableNumber < aTableCount;
927 aTableNumber++)
928 {
929 uint32 aTableOffset = aSchemaSection.at(OffsetTables + AtomSize
930 * aTableNumber);
931 // XXX Set the size boundary on aTableSection.
932 const ReadSection aTableSection =
933 aSchemaSection.subsection(aTableOffset);
934 auto_ptr<Table> aTable(new Table(aTableSection));
935 Table::Id aTableId = aTable->getMetaRecord().dataRecordType();
936 mTableMap.insert(TableMap::value_type(aTableId, aTable.get()));
937 aTable.release();
938 }
939
940 // Fill in the schema for the meta tables.
941
942 findTable(mDb.schemaRelations.DataRecordType).getMetaRecord().
943 setRecordAttributeInfo(mDb.schemaRelations);
944 findTable(mDb.schemaIndexes.DataRecordType).getMetaRecord().
945 setRecordAttributeInfo(mDb.schemaIndexes);
946 findTable(mDb.schemaParsingModule.DataRecordType).getMetaRecord().
947 setRecordAttributeInfo(mDb.schemaParsingModule);
948
949 // OK, we have created all the tables in the tableMap. Now
950 // lets read the schema and proccess it accordingly.
951 // Iterate over all schema records.
952 Table &aTable = findTable(mDb.schemaAttributes.DataRecordType);
953 aTable.getMetaRecord().setRecordAttributeInfo(mDb.schemaAttributes);
954 uint32 aRecordsCount = aTable.getRecordsCount();
955 ReadSection aRecordsSection = aTable.getRecordsSection();
956 uint32 aReadOffset = 0;
957 const MetaRecord &aMetaRecord = aTable.getMetaRecord();
958
959 CSSM_DB_ATTRIBUTE_DATA aRelationIDData =
960 {
961 RelationID,
962 0,
963 NULL
964 };
965 CSSM_DB_ATTRIBUTE_DATA aAttributeIDData =
966 {
967 AttributeID,
968 0,
969 NULL
970 };
971 CSSM_DB_ATTRIBUTE_DATA aAttributeNameFormatData =
972 {
973 AttributeNameFormat,
974 0,
975 NULL
976 };
977 CSSM_DB_ATTRIBUTE_DATA aAttributeNameData =
978 {
979 AttributeName,
980 0,
981 NULL
982 };
983 CSSM_DB_ATTRIBUTE_DATA aAttributeNameIDData =
984 {
985 AttributeNameID,
986 0,
987 NULL
988 };
989 CSSM_DB_ATTRIBUTE_DATA aAttributeFormatData =
990 {
991 AttributeFormat,
992 0,
993 NULL
994 };
995 CSSM_DB_ATTRIBUTE_DATA aRecordAttributes[] =
996 {
997 aRelationIDData,
998 aAttributeIDData,
999 aAttributeNameFormatData,
1000 aAttributeNameData,
1001 aAttributeNameIDData,
1002 aAttributeFormatData
1003 };
1004 CSSM_DB_RECORD_ATTRIBUTE_DATA aRecordAttributeData =
1005 {
1006 aMetaRecord.dataRecordType(),
1007 0,
1008 sizeof(aRecordAttributes) / sizeof(CSSM_DB_ATTRIBUTE_DATA),
1009 aRecordAttributes
1010 };
1011 CssmDbRecordAttributeData &aRecordData = CssmDbRecordAttributeData::overlay(aRecordAttributeData);
1012
1013 TrackingAllocator recordAllocator(Allocator::standard());
1014 for (uint32 aRecord = 0; aRecord != aRecordsCount; aRecord++)
1015 {
1016 ReadSection aRecordSection = MetaRecord::readSection(aRecordsSection, aReadOffset);
1017 uint32 aRecordSize = aRecordSection.size();
1018 aReadOffset += aRecordSize;
1019 aMetaRecord.unpackRecord(aRecordSection, recordAllocator,
1020 &aRecordAttributeData, NULL, 0);
1021 // Create the attribute coresponding to this entry
1022 if (aRecordData[0].size() != 1 || aRecordData[0].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
1023 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1024 uint32 aRelationId = aRecordData[0];
1025
1026 // Skip the schema relations for the meta tables themselves.
1027 // FIXME: this hard-wires the meta-table relation IDs to be
1028 // within {CSSM_DB_RECORDTYPE_SCHEMA_START...
1029 // CSSM_DB_RECORDTYPE_SCHEMA_END} (which is {0..4}).
1030 // Bogus - the MDS schema relation IDs start at
1031 // CSSM_DB_RELATIONID_MDS_START which is 0x40000000.
1032 // Ref. Radar 2817921.
1033 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= aRelationId && aRelationId < CSSM_DB_RECORDTYPE_SCHEMA_END)
1034 continue;
1035
1036 // Get the MetaRecord corresponding to the specified RelationId
1037 MetaRecord &aMetaRecord = findTable(aRelationId).getMetaRecord();
1038
1039 if (aRecordData[1].size() != 1
1040 || aRecordData[1].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1041 || aRecordData[2].size() != 1
1042 || aRecordData[2].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32
1043 || aRecordData[5].size() != 1
1044 || aRecordData[5].format() != CSSM_DB_ATTRIBUTE_FORMAT_UINT32)
1045 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1046
1047 uint32 anAttributeId = aRecordData[1];
1048 uint32 anAttributeNameFormat = aRecordData[2];
1049 uint32 anAttributeFormat = aRecordData[5];
1050 auto_ptr<string> aName;
1051 const CssmData *aNameID = NULL;
1052
1053 if (aRecordData[3].size() == 1)
1054 {
1055 if (aRecordData[3].format() != CSSM_DB_ATTRIBUTE_FORMAT_STRING)
1056 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1057
1058 auto_ptr<string> aName2(new string(static_cast<string>(aRecordData[3])));
1059 aName = aName2;
1060 }
1061
1062 if (aRecordData[4].size() == 1)
1063 {
1064 if (aRecordData[4].format() != CSSM_DB_ATTRIBUTE_FORMAT_BLOB)
1065 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1066
1067 // @@@ Invoking conversion operator to CssmData & on aRecordData[4]
1068 // And taking address of result.
1069 aNameID = &static_cast<const CssmData &>(aRecordData[4]);
1070 }
1071
1072 // Make sure that the attribute specified by anAttributeNameFormat is present.
1073 switch (anAttributeNameFormat)
1074 {
1075 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
1076 if (aRecordData[3].size() != 1)
1077 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1078 break;
1079 case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
1080 if (aRecordData[4].size() != 1)
1081 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1082 break;
1083 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
1084 break;
1085 default:
1086 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1087 }
1088
1089 // Create the attribute
1090 aMetaRecord.createAttribute(aName.get(), aNameID, anAttributeId, anAttributeFormat);
1091 }
1092
1093 // initialize the indexes associated with each table
1094 {
1095 TableMap::iterator it;
1096 for (it = mTableMap.begin(); it != mTableMap.end(); it++)
1097 it->second->readIndexSection();
1098 }
1099 }
1100 catch(...)
1101 {
1102 for_each_map_delete(mTableMap.begin(), mTableMap.end());
1103 mTableMap.clear();
1104 throw;
1105 }
1106 }
1107
1108 const RecordId
1109 DbVersion::getRecord(Table::Id inTableId, const RecordId &inRecordId,
1110 CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
1111 CssmData *inoutData,
1112 Allocator &inAllocator) const
1113 {
1114 return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
1115 inoutData, inAllocator);
1116 }
1117
1118 Cursor *
1119 DbVersion::createCursor(const CSSM_QUERY *inQuery) const
1120 {
1121 // XXX We should add support for these special query types
1122 // By Creating a Cursor that iterates over multiple tables
1123 if (!inQuery || inQuery->RecordType == CSSM_DL_DB_RECORD_ANY
1124 || inQuery->RecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
1125 {
1126 return new MultiCursor(inQuery, *this);
1127 }
1128
1129 return findTable(inQuery->RecordType).createCursor(inQuery, *this);
1130 }
1131
1132 bool DbVersion::hasTable(Table::Id inTableId) const
1133 {
1134 TableMap::const_iterator it = mTableMap.find(inTableId);
1135 return it != mTableMap.end();
1136 }
1137
1138 const Table &
1139 DbVersion::findTable(Table::Id inTableId) const
1140 {
1141 TableMap::const_iterator it = mTableMap.find(inTableId);
1142 if (it == mTableMap.end())
1143 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1144 return *it->second;
1145 }
1146
1147 Table &
1148 DbVersion::findTable(Table::Id inTableId)
1149 {
1150 TableMap::iterator it = mTableMap.find(inTableId);
1151 if (it == mTableMap.end())
1152 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1153 return *it->second;
1154 }
1155
1156 //
1157 // Cursor implemetation
1158 //
1159 Cursor::Cursor()
1160 {
1161 }
1162
1163 Cursor::Cursor(const DbVersion &inDbVersion) : mDbVersion(&inDbVersion)
1164 {
1165 }
1166
1167 Cursor::~Cursor()
1168 {
1169 }
1170
1171 bool
1172 Cursor::next(Table::Id &outTableId,
1173 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
1174 CssmData *outData,
1175 Allocator &inAllocator,
1176 RecordId &recordId)
1177 {
1178 return false;
1179 }
1180
1181 //
1182 // LinearCursor implemetation
1183 //
1184 LinearCursor::LinearCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion,
1185 const Table &inTable) :
1186 Cursor(inDbVersion),
1187 mRecordsCount(inTable.getRecordsCount()),
1188 mRecord(0),
1189 mRecordsSection(inTable.getRecordsSection()),
1190 mReadOffset(0),
1191 mMetaRecord(inTable.getMetaRecord())
1192 {
1193 if (inQuery)
1194 {
1195 mConjunctive = inQuery->Conjunctive;
1196 mQueryFlags = inQuery->QueryFlags;
1197 // XXX Do something with inQuery->QueryLimits?
1198 uint32 aPredicatesCount = inQuery->NumSelectionPredicates;
1199 mPredicates.resize(aPredicatesCount);
1200 try
1201 {
1202 for (uint32 anIndex = 0; anIndex < aPredicatesCount; anIndex++)
1203 {
1204 CSSM_SELECTION_PREDICATE &aPredicate = inQuery->SelectionPredicate[anIndex];
1205 mPredicates[anIndex] = new SelectionPredicate(mMetaRecord, aPredicate);
1206 }
1207 }
1208 catch(...)
1209 {
1210 for_each_delete(mPredicates.begin(), mPredicates.end());
1211 throw;
1212 }
1213 }
1214 }
1215
1216 LinearCursor::~LinearCursor()
1217 {
1218 for_each_delete(mPredicates.begin(), mPredicates.end());
1219 }
1220
1221 bool
1222 LinearCursor::next(Table::Id &outTableId,
1223 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
1224 CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
1225 {
1226 while (mRecord++ < mRecordsCount)
1227 {
1228 ReadSection aRecordSection = MetaRecord::readSection(mRecordsSection, mReadOffset);
1229 uint32 aRecordSize = aRecordSection.size();
1230 mReadOffset += aRecordSize;
1231
1232 PredicateVector::const_iterator anIt = mPredicates.begin();
1233 PredicateVector::const_iterator anEnd = mPredicates.end();
1234 bool aMatch;
1235 if (anIt == anEnd)
1236 {
1237 // If there are no predicates we have a match.
1238 aMatch = true;
1239 }
1240 else if (mConjunctive == CSSM_DB_OR)
1241 {
1242 // If mConjunctive is OR, the first predicate that returns
1243 // true indicates a match. Dropthough means no match
1244 aMatch = false;
1245 for (; anIt != anEnd; anIt++)
1246 {
1247 if ((*anIt)->evaluate(aRecordSection))
1248 {
1249 aMatch = true;
1250 break;
1251 }
1252 }
1253 }
1254 else if (mConjunctive == CSSM_DB_AND || mConjunctive == CSSM_DB_NONE)
1255 {
1256 // If mConjunctive is AND (or NONE), the first predicate that returns
1257 // false indicates a mismatch. Dropthough means a match
1258 aMatch = true;
1259 for (; anIt != anEnd; anIt++)
1260 {
1261 if (!(*anIt)->evaluate(aRecordSection))
1262 {
1263 aMatch = false;
1264 break;
1265 }
1266 }
1267 }
1268 else
1269 {
1270 // XXX Should be CSSMERR_DL_INVALID_QUERY (or CSSMERR_DL_INVALID_CONJUNTIVE).
1271 CssmError::throwMe(CSSMERR_DL_UNSUPPORTED_QUERY);
1272 }
1273
1274 if (aMatch)
1275 {
1276 // Get the actual record.
1277 mMetaRecord.unpackRecord(aRecordSection, inAllocator,
1278 inoutAttributes, inoutData,
1279 mQueryFlags);
1280 outTableId = mMetaRecord.dataRecordType();
1281 recordId = MetaRecord::unpackRecordId(aRecordSection);
1282 return true;
1283 }
1284 }
1285
1286 return false;
1287 }
1288
1289 //
1290 // IndexCursor
1291 //
1292
1293 IndexCursor::IndexCursor(DbQueryKey *queryKey, const DbVersion &inDbVersion,
1294 const Table &table, const DbConstIndex *index) :
1295 Cursor(inDbVersion), mQueryKey(queryKey), mTable(table), mIndex(index)
1296 {
1297 index->performQuery(*queryKey, mBegin, mEnd);
1298 }
1299
1300 IndexCursor::~IndexCursor()
1301 {
1302 // the query key will be deleted automatically, since it's an auto_ptr
1303 }
1304
1305 bool
1306 IndexCursor::next(Table::Id &outTableId,
1307 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR outAttributes,
1308 CssmData *outData,
1309 Allocator &inAllocator, RecordId &recordId)
1310 {
1311 if (mBegin == mEnd)
1312 return false;
1313
1314 ReadSection rs = mIndex->getRecordSection(mBegin++);
1315 const MetaRecord &metaRecord = mTable.getMetaRecord();
1316
1317 outTableId = metaRecord.dataRecordType();
1318 metaRecord.unpackRecord(rs, inAllocator, outAttributes, outData, 0);
1319
1320 recordId = MetaRecord::unpackRecordId(rs);
1321 return true;
1322 }
1323
1324 //
1325 // MultiCursor
1326 //
1327 MultiCursor::MultiCursor(const CSSM_QUERY *inQuery, const DbVersion &inDbVersion) :
1328 Cursor(inDbVersion), mTableIterator(inDbVersion.begin())
1329 {
1330 if (inQuery)
1331 mQuery.reset(new CssmAutoQuery(*inQuery));
1332 else
1333 {
1334 mQuery.reset(new CssmAutoQuery());
1335 mQuery->recordType(CSSM_DL_DB_RECORD_ANY);
1336 }
1337 }
1338
1339 MultiCursor::~MultiCursor()
1340 {
1341 }
1342
1343 bool
1344 MultiCursor::next(Table::Id &outTableId,
1345 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
1346 CssmData *inoutData, Allocator &inAllocator, RecordId &recordId)
1347 {
1348 for (;;)
1349 {
1350 if (!mCursor.get())
1351 {
1352 if (mTableIterator == mDbVersion->end())
1353 return false;
1354
1355 const Table &aTable = *mTableIterator++;
1356 if (!aTable.matchesTableId(mQuery->recordType()))
1357 continue;
1358
1359 mCursor.reset(aTable.createCursor(mQuery.get(), *mDbVersion));
1360 }
1361
1362 if (mCursor->next(outTableId, inoutAttributes, inoutData, inAllocator, recordId))
1363 return true;
1364
1365 mCursor.reset(NULL);
1366 }
1367 }
1368
1369
1370 //
1371 // DbModifier
1372 //
1373 DbModifier::DbModifier(AtomicFile &inAtomicFile, const AppleDatabase &db) :
1374 Metadata(),
1375 mDbVersion(),
1376 mAtomicFile(inAtomicFile),
1377 mDb(db)
1378 {
1379 }
1380
1381 DbModifier::~DbModifier()
1382 {
1383 try
1384 {
1385 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1386 // mAtomicTempFile will do automatic rollback on destruction.
1387 }
1388 catch(...) {}
1389 }
1390
1391 const RefPointer<const DbVersion>
1392 DbModifier::getDbVersion(bool force)
1393 {
1394 StLock<Mutex> _(mDbVersionLock);
1395
1396 /* Initialize the shared memory file change mechanism */
1397 pthread_once(&gCommonInitMutex, initCommon);
1398
1399 /* If we don't have a mDbVersion yet, or we are force to re-read the file
1400 before a write transaction, or we have received any notifications after
1401 the last time we read the file, or more than kForceReReadTime seconds
1402 have passed since the last time we read the file, we open the file and
1403 check if it has changed. */
1404 if (!mDbVersion ||
1405 force ||
1406 gSegment == NULL ||
1407 mNotifyCount != *gSegment ||
1408 CFAbsoluteTimeGetCurrent() > mDbLastRead + kForceReReadTime)
1409 {
1410 RefPointer <AtomicBufferedFile> atomicBufferedFile(mAtomicFile.read());
1411 off_t length = atomicBufferedFile->open();
1412 /* Record the number of notifications we've seen and when we last
1413 opened the file. */
1414 if (gSegment != NULL)
1415 {
1416 mNotifyCount = *gSegment;
1417 }
1418
1419 mDbLastRead = CFAbsoluteTimeGetCurrent();
1420
1421 /* If we already have a mDbVersion, let's check if we can reuse it. */
1422 if (mDbVersion)
1423 {
1424 if (length < AtomSize)
1425 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
1426
1427 off_t bytesRead = 0;
1428 const uint8 *ptr = atomicBufferedFile->read(length - AtomSize,
1429 AtomSize, bytesRead);
1430 ReadSection aVersionSection(ptr, (size_t)bytesRead);
1431 uint32 aVersionId = aVersionSection[0];
1432
1433 /* If the version stamp hasn't changed the old mDbVersion is still
1434 current. */
1435 if (aVersionId == mDbVersion->getVersionId())
1436 return mDbVersion;
1437 }
1438
1439 mDbVersion = new DbVersion(mDb, atomicBufferedFile);
1440 }
1441
1442 return mDbVersion;
1443 }
1444
1445 void
1446 DbModifier::createDatabase(const CSSM_DBINFO &inDbInfo,
1447 const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry,
1448 mode_t mode)
1449 {
1450 // XXX This needs better locking. There is a possible race condition between
1451 // two concurrent creators. Or a writer/creator or a close/create etc.
1452 if (mAtomicTempFile || !mModifiedTableMap.empty())
1453 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
1454
1455 mAtomicTempFile = mAtomicFile.create(mode);
1456 // Set mVersionId to one since this is the first version of the database.
1457 mVersionId = 1;
1458
1459 // we need to create the meta tables first, because inserting tables
1460 // (including the meta tables themselves) relies on them being there
1461 createTable(new MetaRecord(mDb.schemaRelations));
1462 createTable(new MetaRecord(mDb.schemaAttributes));
1463 createTable(new MetaRecord(mDb.schemaIndexes));
1464 createTable(new MetaRecord(mDb.schemaParsingModule));
1465
1466 // now add the meta-tables' schema to the meta tables themselves
1467 insertTableSchema(mDb.schemaRelations);
1468 insertTableSchema(mDb.schemaAttributes);
1469 insertTableSchema(mDb.schemaIndexes);
1470 insertTableSchema(mDb.schemaParsingModule);
1471
1472 if (inInitialAclEntry != NULL)
1473 {
1474 //createACL(*inInitialAclEntry);
1475 }
1476
1477 if (inDbInfo.NumberOfRecordTypes == 0)
1478 return;
1479 if (inDbInfo.RecordAttributeNames == NULL)
1480 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1481 if (inDbInfo.RecordIndexes == NULL)
1482 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_INDEX);
1483 if (inDbInfo.DefaultParsingModules == NULL)
1484 CssmError::throwMe(CSSMERR_DL_INVALID_PARSING_MODULE);
1485
1486 for (uint32 anIndex = 0; anIndex < inDbInfo.NumberOfRecordTypes; anIndex++)
1487 {
1488 insertTable(CssmDbRecordAttributeInfo::overlay(inDbInfo.RecordAttributeNames[anIndex]),
1489 &inDbInfo.RecordIndexes[anIndex],
1490 &inDbInfo.DefaultParsingModules[anIndex]);
1491 }
1492 }
1493
1494 void DbModifier::openDatabase()
1495 {
1496 // No need to do anything on open if we are already writing the database.
1497 if (!mAtomicTempFile)
1498 getDbVersion(false);
1499 }
1500
1501 void DbModifier::closeDatabase()
1502 {
1503 commit(); // XXX Requires write lock.
1504 StLock<Mutex> _(mDbVersionLock);
1505 mDbVersion = NULL;
1506 }
1507
1508 void DbModifier::deleteDatabase()
1509 {
1510 bool isDirty = mAtomicTempFile;
1511 rollback(); // XXX Requires write lock.
1512 StLock<Mutex> _(mDbVersionLock);
1513
1514 // Clean up mModifiedTableMap in case this object gets reused again for
1515 // a new create.
1516 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1517 mModifiedTableMap.clear();
1518
1519 // If the database was dirty and we had no mDbVersion yet then rollback()
1520 // would have deleted the db.
1521 if (!isDirty || mDbVersion)
1522 {
1523 mDbVersion = NULL;
1524 mAtomicFile.performDelete();
1525 }
1526 }
1527
1528 void
1529 DbModifier::modifyDatabase()
1530 {
1531 if (mAtomicTempFile)
1532 return;
1533
1534 try
1535 {
1536 mAtomicTempFile = mAtomicFile.write();
1537 // Now we are holding the write lock make sure we get the latest greatest version of the db.
1538 // Also set mVersionId to one more that that of the old database.
1539 mVersionId = getDbVersion(true)->getVersionId() + 1;
1540
1541 // Never make a database with mVersionId 0 since it makes bad things happen to Jaguar and older systems
1542 if (mVersionId == 0)
1543 mVersionId = 1;
1544
1545 // Remove all old modified tables
1546 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1547 mModifiedTableMap.clear();
1548
1549 // Setup the new tables
1550 DbVersion::TableMap::const_iterator anIt =
1551 mDbVersion->mTableMap.begin();
1552 DbVersion::TableMap::const_iterator anEnd =
1553 mDbVersion->mTableMap.end();
1554 for (; anIt != anEnd; ++anIt)
1555 {
1556 auto_ptr<ModifiedTable> aTable(new ModifiedTable(anIt->second));
1557 mModifiedTableMap.insert(ModifiedTableMap::value_type(anIt->first,
1558 aTable.get()));
1559 aTable.release();
1560 }
1561 }
1562 catch(...)
1563 {
1564 for_each_map_delete(mModifiedTableMap.begin(), mModifiedTableMap.end());
1565 mModifiedTableMap.clear();
1566 rollback();
1567 throw;
1568 }
1569 }
1570
1571 void
1572 DbModifier::deleteRecord(Table::Id inTableId, const RecordId &inRecordId)
1573 {
1574 modifyDatabase();
1575 findTable(inTableId).deleteRecord(inRecordId);
1576 }
1577
1578 const RecordId
1579 DbModifier::insertRecord(Table::Id inTableId,
1580 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
1581 const CssmData *inData)
1582 {
1583 modifyDatabase();
1584 return findTable(inTableId).insertRecord(mVersionId, inAttributes, inData);
1585 }
1586
1587 const RecordId
1588 DbModifier::updateRecord(Table::Id inTableId, const RecordId &inRecordId,
1589 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
1590 const CssmData *inData,
1591 CSSM_DB_MODIFY_MODE inModifyMode)
1592 {
1593 modifyDatabase();
1594 return findTable(inTableId).updateRecord(inRecordId, inAttributes, inData, inModifyMode);
1595 }
1596
1597 // Create a table associated with a given metarecord, and add the table
1598 // to the database.
1599
1600 ModifiedTable *
1601 DbModifier::createTable(MetaRecord *inMetaRecord)
1602 {
1603 auto_ptr<MetaRecord> aMetaRecord(inMetaRecord);
1604 auto_ptr<ModifiedTable> aModifiedTable(new ModifiedTable(inMetaRecord));
1605 // Now that aModifiedTable is fully constructed it owns inMetaRecord
1606 aMetaRecord.release();
1607
1608 if (!mModifiedTableMap.insert
1609 (ModifiedTableMap::value_type(inMetaRecord->dataRecordType(),
1610 aModifiedTable.get())).second)
1611 {
1612 // XXX Should be CSSMERR_DL_DUPLICATE_RECORDTYPE. Since that
1613 // doesn't exist we report that the metatable's unique index would
1614 // no longer be valid
1615 CssmError::throwMe(CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA);
1616 }
1617
1618 return aModifiedTable.release();
1619 }
1620
1621 void
1622 DbModifier::deleteTable(Table::Id inTableId)
1623 {
1624 modifyDatabase();
1625 // Can't delete schema tables.
1626 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= inTableId
1627 && inTableId < CSSM_DB_RECORDTYPE_SCHEMA_END)
1628 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1629
1630 // Find the ModifiedTable and delete it
1631 ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
1632 if (it == mModifiedTableMap.end())
1633 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1634
1635 delete it->second;
1636 mModifiedTableMap.erase(it);
1637 }
1638
1639 uint32
1640 DbModifier::writeAuthSection(uint32 inSectionOffset)
1641 {
1642 WriteSection anAuthSection;
1643
1644 // XXX Put real data into the authsection.
1645 uint32 anOffset = anAuthSection.put(0, 0);
1646 anAuthSection.size(anOffset);
1647
1648 mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
1649 anAuthSection.address(), anAuthSection.size());
1650 return inSectionOffset + anOffset;
1651 }
1652
1653 uint32
1654 DbModifier::writeSchemaSection(uint32 inSectionOffset)
1655 {
1656 uint32 aTableCount = (uint32) mModifiedTableMap.size();
1657 WriteSection aTableSection(Allocator::standard(),
1658 OffsetTables + AtomSize * aTableCount);
1659 // Set aTableSection to the correct size.
1660 aTableSection.size(OffsetTables + AtomSize * aTableCount);
1661 aTableSection.put(OffsetTablesCount, aTableCount);
1662
1663 uint32 anOffset = inSectionOffset + OffsetTables + AtomSize * aTableCount;
1664 ModifiedTableMap::const_iterator anIt = mModifiedTableMap.begin();
1665 ModifiedTableMap::const_iterator anEnd = mModifiedTableMap.end();
1666 for (uint32 aTableNumber = 0; anIt != anEnd; anIt++, aTableNumber++)
1667 {
1668 // Put the offset to the current table relative to the start of
1669 // this section into the tables array
1670 aTableSection.put(OffsetTables + AtomSize * aTableNumber,
1671 anOffset - inSectionOffset);
1672 anOffset = anIt->second->writeTable(*mAtomicTempFile, anOffset);
1673 }
1674
1675 aTableSection.put(OffsetSchemaSize, anOffset - inSectionOffset);
1676 mAtomicTempFile->write(AtomicFile::FromStart, inSectionOffset,
1677 aTableSection.address(), aTableSection.size());
1678
1679 return anOffset;
1680 }
1681
1682 void
1683 DbModifier::commit()
1684 {
1685 if (!mAtomicTempFile)
1686 return;
1687 try
1688 {
1689 WriteSection aHeaderSection(Allocator::standard(), size_t(HeaderSize));
1690 // Set aHeaderSection to the correct size.
1691 aHeaderSection.size(HeaderSize);
1692
1693 // Start writing sections after the header
1694 uint32 anOffset = HeaderOffset + HeaderSize;
1695
1696 // Write auth section
1697 aHeaderSection.put(OffsetAuthOffset, anOffset);
1698 anOffset = writeAuthSection(anOffset);
1699 // Write schema section
1700 aHeaderSection.put(OffsetSchemaOffset, anOffset);
1701 anOffset = writeSchemaSection(anOffset);
1702
1703 // Write out the file header.
1704 aHeaderSection.put(OffsetMagic, HeaderMagic);
1705 aHeaderSection.put(OffsetVersion, HeaderVersion);
1706 mAtomicTempFile->write(AtomicFile::FromStart, HeaderOffset,
1707 aHeaderSection.address(), aHeaderSection.size());
1708
1709 // Write out the versionId.
1710 WriteSection aVersionSection(Allocator::standard(), size_t(AtomSize));
1711 anOffset = aVersionSection.put(0, mVersionId);
1712 aVersionSection.size(anOffset);
1713
1714 mAtomicTempFile->write(AtomicFile::FromEnd, 0,
1715 aVersionSection.address(), aVersionSection.size());
1716
1717 mAtomicTempFile->commit();
1718 mAtomicTempFile = NULL;
1719 /* Initialize the shared memory file change mechanism */
1720 pthread_once(&gCommonInitMutex, initCommon);
1721
1722 if (gSegment != NULL)
1723 {
1724 /*
1725 PLEASE NOTE:
1726
1727 The following operation is endian safe because we are not looking
1728 for monotonic increase. I have tested every possible value of
1729 *gSegment, and there is no value for which alternating
1730 big and little endian increments will produce the original value.
1731 */
1732
1733 OSAtomicIncrement32Barrier (gSegment);
1734 }
1735 }
1736 catch(...)
1737 {
1738 rollback();
1739 throw;
1740 }
1741 }
1742
1743 void
1744 DbModifier::rollback() throw()
1745 {
1746 // This will destroy the AtomicTempFile if we have one causing it to rollback.
1747 mAtomicTempFile = NULL;
1748 }
1749
1750 const RecordId
1751 DbModifier::getRecord(Table::Id inTableId, const RecordId &inRecordId,
1752 CSSM_DB_RECORD_ATTRIBUTE_DATA *inoutAttributes,
1753 CssmData *inoutData, Allocator &inAllocator)
1754 {
1755 if (mAtomicTempFile)
1756 {
1757 // We are in the midst of changing the database.
1758 return findTable(inTableId).getRecord(inRecordId, inoutAttributes,
1759 inoutData, inAllocator);
1760 }
1761 else
1762 {
1763 return getDbVersion(false)->getRecord(inTableId, inRecordId,
1764 inoutAttributes, inoutData, inAllocator);
1765 }
1766 }
1767
1768 Cursor *
1769 DbModifier::createCursor(const CSSM_QUERY *inQuery)
1770 {
1771 if (mAtomicTempFile)
1772 {
1773 // We are modifying this database.
1774
1775 // If we have a mDbVersion already then it's a snapshot of the database
1776 // right before the modifications started. So return a cursor using
1777 // that.
1778 if (mDbVersion)
1779 return mDbVersion->createCursor(inQuery);
1780
1781 // This is a newly created but never commited database. Return a
1782 // Cursor that will not return any matches.
1783 return new Cursor();
1784 }
1785
1786 // Get the latest and greatest version of the db and create the cursor
1787 // on that.
1788 return getDbVersion(false)->createCursor(inQuery);
1789 }
1790
1791 // Insert schema records for a new table into the metatables of the database. This gets
1792 // called while a database is being created.
1793
1794 void
1795 DbModifier::insertTableSchema(const CssmDbRecordAttributeInfo &inInfo,
1796 const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */)
1797 {
1798 ModifiedTable &aTable = findTable(inInfo.DataRecordType);
1799 const MetaRecord &aMetaRecord = aTable.getMetaRecord();
1800
1801 CssmAutoDbRecordAttributeData aRecordBuilder(5); // Set capacity to 5 so we don't need to grow
1802
1803 // Create the entry for the SchemaRelations table.
1804 aRecordBuilder.add(RelationID, inInfo.recordType());
1805 aRecordBuilder.add(RelationName, mDb.recordName(inInfo.recordType()));
1806
1807 // Insert the record into the SchemaRelations ModifiedTable
1808 findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
1809 &aRecordBuilder, NULL);
1810
1811 ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
1812 for (uint32 anIndex = 0; anIndex < inInfo.size(); anIndex++)
1813 {
1814 // Create an entry for the SchemaAttributes table.
1815 aRecordBuilder.clear();
1816 aRecordBuilder.add(RelationID, inInfo.recordType());
1817 aRecordBuilder.add(AttributeNameFormat, inInfo.at(anIndex).nameFormat());
1818
1819 uint32 attributeId = aMetaRecord.metaAttribute(inInfo.at(anIndex)).attributeId();
1820
1821 switch (inInfo.at(anIndex).nameFormat())
1822 {
1823 case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
1824 aRecordBuilder.add(AttributeName, inInfo.at(anIndex).Label.AttributeName);
1825 break;
1826 case CSSM_DB_ATTRIBUTE_NAME_AS_OID:
1827 aRecordBuilder.add(AttributeNameID, inInfo.at(anIndex).Label.AttributeOID);
1828 break;
1829 case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
1830 break;
1831 default:
1832 CssmError::throwMe(CSSMERR_DL_INVALID_FIELD_NAME);
1833 }
1834
1835 aRecordBuilder.add(AttributeID, attributeId);
1836 aRecordBuilder.add(AttributeFormat, inInfo.at(anIndex).format());
1837
1838 // Insert the record into the SchemaAttributes ModifiedTable
1839 anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1840 }
1841
1842 if (inIndexInfo != NULL) {
1843
1844 if (inIndexInfo->DataRecordType != inInfo.DataRecordType &&
1845 inIndexInfo->NumberOfIndexes > 0)
1846 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1847
1848 ModifiedTable &indexMetaTable = findTable(mDb.schemaIndexes.DataRecordType);
1849 uint32 aNumberOfIndexes = inIndexInfo->NumberOfIndexes;
1850
1851 for (uint32 anIndex = 0; anIndex < aNumberOfIndexes; anIndex++)
1852 {
1853 const CssmDbIndexInfo &thisIndex = CssmDbIndexInfo::overlay(inIndexInfo->IndexInfo[anIndex]);
1854
1855 // make sure the index is supported
1856 if (thisIndex.dataLocation() != CSSM_DB_INDEX_ON_ATTRIBUTE)
1857 CssmError::throwMe(CSSMERR_DL_INVALID_INDEX_INFO);
1858
1859 // assign an index ID: the unique index is ID 0, all others are ID > 0
1860 uint32 indexId;
1861 if (thisIndex.IndexType == CSSM_DB_INDEX_UNIQUE)
1862 indexId = 0;
1863 else
1864 indexId = anIndex + 1;
1865
1866 // figure out the attribute ID
1867 uint32 attributeId =
1868 aMetaRecord.metaAttribute(thisIndex.Info).attributeId();
1869
1870 // Create an entry for the SchemaIndexes table.
1871 aRecordBuilder.clear();
1872 aRecordBuilder.add(RelationID, inInfo.DataRecordType);
1873 aRecordBuilder.add(IndexID, indexId);
1874 aRecordBuilder.add(AttributeID, attributeId);
1875 aRecordBuilder.add(IndexType, thisIndex.IndexType);
1876 aRecordBuilder.add(IndexedDataLocation, thisIndex.IndexedDataLocation);
1877
1878 // Insert the record into the SchemaIndexes ModifiedTable
1879 indexMetaTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1880
1881 // update the table's index objects
1882 DbMutableIndex &index = aTable.findIndex(indexId, aMetaRecord, indexId == 0);
1883 index.appendAttribute(attributeId);
1884 }
1885 }
1886 }
1887
1888 // Insert a new table. The attribute info is required; the index and parsing module
1889 // descriptions are optional. This version gets called during the creation of a
1890 // database.
1891
1892 void
1893 DbModifier::insertTable(const CssmDbRecordAttributeInfo &inInfo,
1894 const CSSM_DB_RECORD_INDEX_INFO *inIndexInfo /* = NULL */,
1895 const CSSM_DB_PARSING_MODULE_INFO *inParsingModule /* = NULL */)
1896 {
1897 modifyDatabase();
1898 createTable(new MetaRecord(inInfo));
1899 insertTableSchema(inInfo, inIndexInfo);
1900 }
1901
1902 // Insert a new table. This is the version that gets called when a table is added
1903 // after a database has been created.
1904
1905 void
1906 DbModifier::insertTable(Table::Id inTableId, const string &inTableName,
1907 uint32 inNumberOfAttributes,
1908 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
1909 uint32 inNumberOfIndexes,
1910 const CSSM_DB_SCHEMA_INDEX_INFO *inIndexInfo)
1911 {
1912 modifyDatabase();
1913 ModifiedTable *aTable = createTable(new MetaRecord(inTableId, inNumberOfAttributes, inAttributeInfo));
1914
1915 CssmAutoDbRecordAttributeData aRecordBuilder(6); // Set capacity to 6 so we don't need to grow
1916
1917 // Create the entry for the SchemaRelations table.
1918 aRecordBuilder.add(RelationID, inTableId);
1919 aRecordBuilder.add(RelationName, inTableName);
1920
1921 // Insert the record into the SchemaRelations ModifiedTable
1922 findTable(mDb.schemaRelations.DataRecordType).insertRecord(mVersionId,
1923 &aRecordBuilder, NULL);
1924
1925 ModifiedTable &anAttributeTable = findTable(mDb.schemaAttributes.DataRecordType);
1926 for (uint32 anIndex = 0; anIndex < inNumberOfAttributes; anIndex++)
1927 {
1928 // Create an entry for the SchemaAttributes table.
1929 aRecordBuilder.clear();
1930 aRecordBuilder.add(RelationID, inTableId);
1931 // XXX What should this be? We set it to CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER for now
1932 // since the AttributeID is always valid.
1933 aRecordBuilder.add(AttributeNameFormat, uint32(CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER));
1934 aRecordBuilder.add(AttributeID, inAttributeInfo[anIndex].AttributeId);
1935 if (inAttributeInfo[anIndex].AttributeName)
1936 aRecordBuilder.add(AttributeName, inAttributeInfo[anIndex].AttributeName);
1937 if (inAttributeInfo[anIndex].AttributeNameID.Length > 0)
1938 aRecordBuilder.add(AttributeNameID, inAttributeInfo[anIndex].AttributeNameID);
1939 aRecordBuilder.add(AttributeFormat, inAttributeInfo[anIndex].DataType);
1940
1941 // Insert the record into the SchemaAttributes ModifiedTable
1942 anAttributeTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1943 }
1944
1945 ModifiedTable &anIndexTable = findTable(mDb.schemaIndexes.DataRecordType);
1946 for (uint32 anIndex = 0; anIndex < inNumberOfIndexes; anIndex++)
1947 {
1948 // Create an entry for the SchemaIndexes table.
1949 aRecordBuilder.clear();
1950 aRecordBuilder.add(RelationID, inTableId);
1951 aRecordBuilder.add(IndexID, inIndexInfo[anIndex].IndexId);
1952 aRecordBuilder.add(AttributeID, inIndexInfo[anIndex].AttributeId);
1953 aRecordBuilder.add(IndexType, inIndexInfo[anIndex].IndexType);
1954 aRecordBuilder.add(IndexedDataLocation, inIndexInfo[anIndex].IndexedDataLocation);
1955
1956 // Insert the record into the SchemaIndexes ModifiedTable
1957 anIndexTable.insertRecord(mVersionId, &aRecordBuilder, NULL);
1958
1959 // update the table's index objects
1960 DbMutableIndex &index = aTable->findIndex(inIndexInfo[anIndex].IndexId,
1961 aTable->getMetaRecord(), inIndexInfo[anIndex].IndexType == CSSM_DB_INDEX_UNIQUE);
1962 index.appendAttribute(inIndexInfo[anIndex].AttributeId);
1963 }
1964 }
1965
1966
1967
1968 bool DbModifier::hasTable(Table::Id inTableId)
1969 {
1970 return getDbVersion(false)->hasTable(inTableId);
1971 }
1972
1973
1974
1975 ModifiedTable &
1976 DbModifier::findTable(Table::Id inTableId)
1977 {
1978 ModifiedTableMap::iterator it = mModifiedTableMap.find(inTableId);
1979 if (it == mModifiedTableMap.end())
1980 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
1981 return *it->second;
1982 }
1983
1984
1985 //
1986 // AppleDatabaseManager implementation
1987 //
1988
1989 AppleDatabaseManager::AppleDatabaseManager(const AppleDatabaseTableName *tableNames)
1990 : DatabaseManager(),
1991 mTableNames(tableNames)
1992 {
1993 // make sure that a proper set of table ids and names has been provided
1994
1995 if (!mTableNames)
1996 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
1997 else {
1998 uint32 i;
1999 for (i = 0; mTableNames[i].mTableName; i++) {}
2000 if (i < AppleDatabaseTableName::kNumRequiredTableNames)
2001 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
2002 }
2003 }
2004
2005 Database *
2006 AppleDatabaseManager::make(const DbName &inDbName)
2007 {
2008 return new AppleDatabase(inDbName, mTableNames);
2009 }
2010
2011
2012 //
2013 // AppleDbContext implementation
2014 //
2015
2016 /* This is the version 0 CSSM_APPLEDL_OPEN_PARAMETERS struct used up to 10.2.x. */
2017 extern "C" {
2018
2019 typedef struct cssm_appledl_open_parameters_v0
2020 {
2021 uint32 length; /* Should be sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0). */
2022 uint32 version; /* Should be 0. */
2023 CSSM_BOOL autoCommit;
2024 } CSSM_APPLEDL_OPEN_PARAMETERS_V0;
2025
2026 };
2027
2028 AppleDbContext::AppleDbContext(Database &inDatabase,
2029 DatabaseSession &inDatabaseSession,
2030 CSSM_DB_ACCESS_TYPE inAccessRequest,
2031 const AccessCredentials *inAccessCred,
2032 const void *inOpenParameters) :
2033 DbContext(inDatabase, inDatabaseSession, inAccessRequest, inAccessCred),
2034 mAutoCommit(true),
2035 mMode(0666)
2036 {
2037 const CSSM_APPLEDL_OPEN_PARAMETERS *anOpenParameters =
2038 reinterpret_cast<const CSSM_APPLEDL_OPEN_PARAMETERS *>(inOpenParameters);
2039
2040 if (anOpenParameters)
2041 {
2042 switch (anOpenParameters->version)
2043 {
2044 case 1:
2045 if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS))
2046 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2047
2048 if (anOpenParameters->mask & kCSSM_APPLEDL_MASK_MODE)
2049 mMode = anOpenParameters->mode;
2050 /*DROPTHROUGH*/
2051 case 0:
2052 if (anOpenParameters->length < sizeof(CSSM_APPLEDL_OPEN_PARAMETERS_V0))
2053 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2054
2055 mAutoCommit = anOpenParameters->autoCommit == CSSM_FALSE ? false : true;
2056 break;
2057
2058 default:
2059 CssmError::throwMe(CSSMERR_APPLEDL_INVALID_OPEN_PARAMETERS);
2060 }
2061 }
2062 }
2063
2064 AppleDbContext::~AppleDbContext()
2065 {
2066 }
2067
2068 //
2069 // AppleDatabase implementation
2070 //
2071 AppleDatabase::AppleDatabase(const DbName &inDbName, const AppleDatabaseTableName *tableNames) :
2072 Database(inDbName),
2073 schemaRelations(tableNames[AppleDatabaseTableName::kSchemaInfo].mTableId,
2074 sizeof(AttrSchemaRelations) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2075 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaRelations)),
2076 schemaAttributes(tableNames[AppleDatabaseTableName::kSchemaAttributes].mTableId,
2077 sizeof(AttrSchemaAttributes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2078 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaAttributes)),
2079 schemaIndexes(tableNames[AppleDatabaseTableName::kSchemaIndexes].mTableId,
2080 sizeof(AttrSchemaIndexes) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2081 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaIndexes)),
2082 schemaParsingModule(tableNames[AppleDatabaseTableName::kSchemaParsingModule].mTableId,
2083 sizeof(AttrSchemaParsingModule) / sizeof(CSSM_DB_ATTRIBUTE_INFO),
2084 const_cast<CSSM_DB_ATTRIBUTE_INFO_PTR>(AttrSchemaParsingModule)),
2085 mAtomicFile(mDbName.dbName()),
2086 mDbModifier(mAtomicFile, *this),
2087 mTableNames(tableNames)
2088 {
2089 /* temp check for X509Anchors access - this should removed before Leopard GM */
2090 if(!strcmp(inDbName.dbName(), "/System/Library/Keychains/X509Anchors")) {
2091 Syslog::alert("Warning: accessing obsolete X509Anchors.");
2092 }
2093 }
2094
2095 AppleDatabase::~AppleDatabase()
2096 {
2097 }
2098
2099 // Return the name of a record type. This uses a table that maps record types
2100 // to record names. The table is provided when the database is created.
2101
2102 const char *AppleDatabase::recordName(CSSM_DB_RECORDTYPE inRecordType) const
2103 {
2104 if (inRecordType == CSSM_DL_DB_RECORD_ANY || inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS)
2105 CssmError::throwMe(CSSMERR_DL_INVALID_RECORDTYPE);
2106
2107 for (uint32 i = 0; mTableNames[i].mTableName; i++)
2108 if (mTableNames[i].mTableId == inRecordType)
2109 return mTableNames[i].mTableName;
2110
2111 return "";
2112 }
2113
2114 DbContext *
2115 AppleDatabase::makeDbContext(DatabaseSession &inDatabaseSession,
2116 CSSM_DB_ACCESS_TYPE inAccessRequest,
2117 const AccessCredentials *inAccessCred,
2118 const void *inOpenParameters)
2119 {
2120 return new AppleDbContext(*this, inDatabaseSession, inAccessRequest,
2121 inAccessCred, inOpenParameters);
2122 }
2123
2124 void
2125 AppleDatabase::dbCreate(DbContext &inDbContext, const CSSM_DBINFO &inDBInfo,
2126 const CSSM_ACL_ENTRY_INPUT *inInitialAclEntry)
2127 {
2128 AppleDbContext &context = safer_cast<AppleDbContext &>(inDbContext);
2129 try
2130 {
2131 StLock<Mutex> _(mWriteLock);
2132 mDbModifier.createDatabase(inDBInfo, inInitialAclEntry, context.mode());
2133 }
2134 catch(...)
2135 {
2136 mDbModifier.rollback();
2137 throw;
2138 }
2139 if (context.autoCommit())
2140 mDbModifier.commit();
2141 }
2142
2143 void
2144 AppleDatabase::dbOpen(DbContext &inDbContext)
2145 {
2146 mDbModifier.openDatabase();
2147 }
2148
2149 void
2150 AppleDatabase::dbClose()
2151 {
2152 StLock<Mutex> _(mWriteLock);
2153 mDbModifier.closeDatabase();
2154 }
2155
2156 void
2157 AppleDatabase::dbDelete(DatabaseSession &inDatabaseSession,
2158 const AccessCredentials *inAccessCred)
2159 {
2160 StLock<Mutex> _(mWriteLock);
2161 // XXX Check callers credentials.
2162 mDbModifier.deleteDatabase();
2163 }
2164
2165 void
2166 AppleDatabase::createRelation(DbContext &inDbContext,
2167 CSSM_DB_RECORDTYPE inRelationID,
2168 const char *inRelationName,
2169 uint32 inNumberOfAttributes,
2170 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *inAttributeInfo,
2171 uint32 inNumberOfIndexes,
2172 const CSSM_DB_SCHEMA_INDEX_INFO &inIndexInfo)
2173 {
2174 try
2175 {
2176 StLock<Mutex> _(mWriteLock);
2177 // XXX Fix the refs here.
2178 mDbModifier.insertTable(inRelationID, inRelationName,
2179 inNumberOfAttributes, inAttributeInfo,
2180 inNumberOfIndexes, &inIndexInfo);
2181 }
2182 catch(...)
2183 {
2184 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2185 mDbModifier.rollback();
2186 throw;
2187 }
2188 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2189 mDbModifier.commit();
2190 }
2191
2192 void
2193 AppleDatabase::destroyRelation(DbContext &inDbContext,
2194 CSSM_DB_RECORDTYPE inRelationID)
2195 {
2196 try
2197 {
2198 StLock<Mutex> _(mWriteLock);
2199 mDbModifier.deleteTable(inRelationID);
2200 }
2201 catch(...)
2202 {
2203 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2204 mDbModifier.rollback();
2205 throw;
2206 }
2207 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2208 mDbModifier.commit();
2209 }
2210
2211 void
2212 AppleDatabase::authenticate(DbContext &inDbContext,
2213 CSSM_DB_ACCESS_TYPE inAccessRequest,
2214 const AccessCredentials &inAccessCred)
2215 {
2216 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2217 }
2218
2219 void
2220 AppleDatabase::getDbAcl(DbContext &inDbContext,
2221 const CSSM_STRING *inSelectionTag,
2222 uint32 &outNumberOfAclInfos,
2223 CSSM_ACL_ENTRY_INFO_PTR &outAclInfos)
2224 {
2225 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2226 }
2227
2228 void
2229 AppleDatabase::changeDbAcl(DbContext &inDbContext,
2230 const AccessCredentials &inAccessCred,
2231 const CSSM_ACL_EDIT &inAclEdit)
2232 {
2233 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2234 }
2235
2236 void
2237 AppleDatabase::getDbOwner(DbContext &inDbContext,
2238 CSSM_ACL_OWNER_PROTOTYPE &outOwner)
2239 {
2240 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2241 }
2242
2243 void
2244 AppleDatabase::changeDbOwner(DbContext &inDbContext,
2245 const AccessCredentials &inAccessCred,
2246 const CSSM_ACL_OWNER_PROTOTYPE &inNewOwner)
2247 {
2248 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2249 }
2250
2251 char *
2252 AppleDatabase::getDbNameFromHandle(const DbContext &inDbContext) const
2253 {
2254 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2255 }
2256
2257 CSSM_DB_UNIQUE_RECORD_PTR
2258 AppleDatabase::dataInsert(DbContext &inDbContext,
2259 CSSM_DB_RECORDTYPE inRecordType,
2260 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributes,
2261 const CssmData *inData)
2262 {
2263 CSSM_DB_UNIQUE_RECORD_PTR anUniqueRecordPtr = NULL;
2264 try
2265 {
2266 StLock<Mutex> _(mWriteLock);
2267 const RecordId aRecordId =
2268 mDbModifier.insertRecord(inRecordType, inAttributes, inData);
2269
2270 anUniqueRecordPtr = createUniqueRecord(inDbContext, inRecordType,
2271 aRecordId);
2272 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2273 mDbModifier.commit();
2274 }
2275 catch(...)
2276 {
2277 if (anUniqueRecordPtr != NULL)
2278 freeUniqueRecord(inDbContext, *anUniqueRecordPtr);
2279
2280 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2281 mDbModifier.rollback();
2282 throw;
2283 }
2284
2285 return anUniqueRecordPtr;
2286 }
2287
2288 void
2289 AppleDatabase::dataDelete(DbContext &inDbContext,
2290 const CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
2291 {
2292 try
2293 {
2294 // syslog if it's the .Mac password
2295 CSSM_DB_RECORD_ATTRIBUTE_DATA attrData;
2296 // 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
2297 // an exception.
2298 memset(&attrData, 0, sizeof(attrData));
2299 dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
2300
2301 if (attrData.DataRecordType == CSSM_DL_DB_RECORD_GENERIC_PASSWORD)
2302 {
2303 CSSM_DB_ATTRIBUTE_DATA attributes;
2304
2305 // setup some attributes and see if we are indeed the .Mac password
2306 attributes.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
2307 attributes.Info.Label.AttributeID = 'svce';
2308 attributes.Info.AttributeFormat = 0;
2309 attributes.NumberOfValues = 1;
2310 attributes.Value = NULL;
2311
2312 attrData.NumberOfAttributes = 1;
2313 attrData.AttributeData = &attributes;
2314
2315 dataGetFromUniqueRecordId(inDbContext, inUniqueRecord, &attrData, NULL);
2316
2317 // now check the results
2318 std::string dataString((const char*) attrData.AttributeData[0].Value[0].Data, attrData.AttributeData[0].Value[0].Length);
2319 if (dataString == "iTools")
2320 {
2321 syslog(LOG_WARNING, "Warning: Removed .Me password");
2322 }
2323
2324 free(attrData.AttributeData[0].Value[0].Data);
2325 free(attrData.AttributeData[0].Value);
2326 }
2327
2328 StLock<Mutex> _(mWriteLock);
2329 Table::Id aTableId;
2330 const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
2331 mDbModifier.deleteRecord(aTableId, aRecordId);
2332 }
2333 catch(...)
2334 {
2335 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2336 mDbModifier.rollback();
2337 throw;
2338 }
2339
2340 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2341 mDbModifier.commit();
2342 }
2343
2344 void
2345 AppleDatabase::dataModify(DbContext &inDbContext,
2346 CSSM_DB_RECORDTYPE inRecordType,
2347 CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord,
2348 const CSSM_DB_RECORD_ATTRIBUTE_DATA *inAttributesToBeModified,
2349 const CssmData *inDataToBeModified,
2350 CSSM_DB_MODIFY_MODE inModifyMode)
2351 {
2352 try
2353 {
2354 StLock<Mutex> _(mWriteLock);
2355 Table::Id aTableId;
2356 const RecordId oldRecordId = parseUniqueRecord(inoutUniqueRecord,
2357 aTableId);
2358 #if 1
2359 if (inRecordType != aTableId)
2360 #else
2361 if (inRecordType != aTableId &&
2362 inRecordType != CSSM_DL_DB_RECORD_ANY &&
2363 !(inRecordType == CSSM_DL_DB_RECORD_ALL_KEYS &&
2364 (aTableId == CSSM_DL_DB_RECORD_PUBLIC_KEY ||
2365 aTableId == CSSM_DL_DB_RECORD_PRIVATE_KEY ||
2366 aTableId == CSSM_DL_DB_RECORD_SYMMETRIC_KEY)))
2367 #endif
2368 {
2369 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
2370 }
2371
2372 const RecordId newRecordId =
2373 mDbModifier.updateRecord(aTableId,
2374 oldRecordId,
2375 inAttributesToBeModified,
2376 inDataToBeModified,
2377 inModifyMode);
2378 updateUniqueRecord(inDbContext, aTableId, newRecordId,
2379 inoutUniqueRecord);
2380 }
2381 catch(...)
2382 {
2383 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2384 mDbModifier.rollback();
2385 throw;
2386 }
2387
2388 if (safer_cast<AppleDbContext &>(inDbContext).autoCommit())
2389 mDbModifier.commit();
2390 }
2391
2392 CSSM_HANDLE
2393 AppleDatabase::dataGetFirst(DbContext &inDbContext,
2394 const CssmQuery *inQuery,
2395 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2396 CssmData *inoutData,
2397 CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
2398 {
2399 // XXX: register Cursor with DbContext and have DbContext call
2400 // dataAbortQuery for all outstanding Query objects on close.
2401 auto_ptr<Cursor> aCursor(mDbModifier.createCursor(inQuery));
2402 Table::Id aTableId;
2403 RecordId aRecordId;
2404
2405 if (!aCursor->next(aTableId, inoutAttributes, inoutData,
2406 inDbContext.mDatabaseSession, aRecordId))
2407 // return a NULL handle, and implicitly delete the cursor
2408 return CSSM_INVALID_HANDLE;
2409
2410 outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);
2411 return aCursor.release()->handle(); // We didn't throw so keep the Cursor around.
2412 }
2413
2414 bool
2415 AppleDatabase::dataGetNext(DbContext &inDbContext,
2416 CSSM_HANDLE inResultsHandle,
2417 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2418 CssmData *inoutData,
2419 CSSM_DB_UNIQUE_RECORD_PTR &outUniqueRecord)
2420 {
2421 auto_ptr<Cursor> aCursor(&HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE));
2422 Table::Id aTableId;
2423 RecordId aRecordId;
2424
2425 if (!aCursor->next(aTableId, inoutAttributes, inoutData, inDbContext.mDatabaseSession, aRecordId))
2426 return false;
2427
2428 outUniqueRecord = createUniqueRecord(inDbContext, aTableId, aRecordId);
2429
2430 aCursor.release();
2431 return true;
2432 }
2433
2434 void
2435 AppleDatabase::dataAbortQuery(DbContext &inDbContext,
2436 CSSM_HANDLE inResultsHandle)
2437 {
2438 delete &HandleObject::find<Cursor>(inResultsHandle, CSSMERR_DL_INVALID_RESULTS_HANDLE);
2439 }
2440
2441 void
2442 AppleDatabase::dataGetFromUniqueRecordId(DbContext &inDbContext,
2443 const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
2444 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR inoutAttributes,
2445 CssmData *inoutData)
2446 {
2447 Table::Id aTableId;
2448 const RecordId aRecordId(parseUniqueRecord(inUniqueRecord, aTableId));
2449 // XXX Change CDSA spec to use new RecordId returned by this function
2450 mDbModifier.getRecord(aTableId, aRecordId, inoutAttributes, inoutData,
2451 inDbContext.mDatabaseSession);
2452 }
2453
2454 void
2455 AppleDatabase::freeUniqueRecord(DbContext &inDbContext,
2456 CSSM_DB_UNIQUE_RECORD &inUniqueRecord)
2457 {
2458 if (inUniqueRecord.RecordIdentifier.Length != 0
2459 && inUniqueRecord.RecordIdentifier.Data != NULL)
2460 {
2461 inUniqueRecord.RecordIdentifier.Length = 0;
2462 inDbContext.mDatabaseSession.free(inUniqueRecord.RecordIdentifier.Data);
2463 }
2464 inDbContext.mDatabaseSession.free(&inUniqueRecord);
2465 }
2466
2467 void
2468 AppleDatabase::updateUniqueRecord(DbContext &inDbContext,
2469 CSSM_DB_RECORDTYPE inTableId,
2470 const RecordId &inRecordId,
2471 CSSM_DB_UNIQUE_RECORD &inoutUniqueRecord)
2472 {
2473 uint32 *aBuffer = reinterpret_cast<uint32 *>(inoutUniqueRecord.RecordIdentifier.Data);
2474 aBuffer[0] = inTableId;
2475 aBuffer[1] = inRecordId.mRecordNumber;
2476 aBuffer[2] = inRecordId.mCreateVersion;
2477 aBuffer[3] = inRecordId.mRecordVersion;
2478 }
2479
2480 CSSM_DB_UNIQUE_RECORD_PTR
2481 AppleDatabase::createUniqueRecord(DbContext &inDbContext,
2482 CSSM_DB_RECORDTYPE inTableId,
2483 const RecordId &inRecordId)
2484 {
2485 CSSM_DB_UNIQUE_RECORD_PTR aUniqueRecord =
2486 inDbContext.mDatabaseSession.alloc<CSSM_DB_UNIQUE_RECORD>();
2487 memset(aUniqueRecord, 0, sizeof(*aUniqueRecord));
2488 aUniqueRecord->RecordIdentifier.Length = sizeof(uint32) * 4;
2489 try
2490 {
2491 aUniqueRecord->RecordIdentifier.Data =
2492 inDbContext.mDatabaseSession.alloc<uint8>(sizeof(uint32) * 4);
2493 updateUniqueRecord(inDbContext, inTableId, inRecordId, *aUniqueRecord);
2494 }
2495 catch(...)
2496 {
2497 inDbContext.mDatabaseSession.free(aUniqueRecord);
2498 throw;
2499 }
2500
2501 return aUniqueRecord;
2502 }
2503
2504 const RecordId
2505 AppleDatabase::parseUniqueRecord(const CSSM_DB_UNIQUE_RECORD &inUniqueRecord,
2506 CSSM_DB_RECORDTYPE &outTableId)
2507 {
2508 if (inUniqueRecord.RecordIdentifier.Length != sizeof(uint32) * 4)
2509 CssmError::throwMe(CSSMERR_DL_INVALID_RECORD_UID);
2510
2511 uint32 *aBuffer = reinterpret_cast<uint32 *>(inUniqueRecord.RecordIdentifier.Data);
2512 outTableId = aBuffer[0];
2513 return RecordId(aBuffer[1], aBuffer[2], aBuffer[3]);
2514 }
2515
2516 void
2517 AppleDatabase::passThrough(DbContext &dbContext,
2518 uint32 passThroughId,
2519 const void *inputParams,
2520 void **outputParams)
2521 {
2522 switch (passThroughId)
2523 {
2524 case CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT:
2525 {
2526 AppleDbContext &dbc = safer_cast<AppleDbContext &>(dbContext);
2527 // Return the old state of the autoCommit flag if requested
2528 if (outputParams)
2529 *reinterpret_cast<CSSM_BOOL *>(outputParams) = dbc.autoCommit();
2530 dbc.autoCommit(inputParams ? CSSM_TRUE : CSSM_FALSE);
2531 }
2532 break;
2533
2534 case CSSM_APPLEFILEDL_COMMIT:
2535 mDbModifier.commit();
2536 break;
2537
2538 case CSSM_APPLEFILEDL_ROLLBACK:
2539 mDbModifier.rollback();
2540 break;
2541
2542 case CSSM_APPLECSPDL_DB_RELATION_EXISTS:
2543 {
2544 CSSM_BOOL returnValue;
2545
2546 CSSM_DB_RECORDTYPE recordType = *(CSSM_DB_RECORDTYPE*) inputParams;
2547 if (recordType == CSSM_DL_DB_RECORD_ANY || recordType == CSSM_DL_DB_RECORD_ALL_KEYS)
2548 {
2549 returnValue = CSSM_TRUE;
2550 }
2551 else
2552 {
2553 returnValue = mDbModifier.hasTable(recordType);
2554 }
2555
2556 *(CSSM_BOOL*) outputParams = returnValue;
2557 break;
2558 }
2559
2560 default:
2561 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
2562 break;
2563 }
2564 }