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