]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
1 | /* |
2 | * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
427c49bc A |
24 | |
25 | #include "SecDb.h" | |
26 | #include "debugging.h" | |
27 | ||
28 | #include <sqlite3.h> | |
29 | #include <sqlite3_private.h> | |
30 | #include <CoreFoundation/CoreFoundation.h> | |
31 | #include <libgen.h> | |
32 | #include <sys/stat.h> | |
33 | #include <AssertMacros.h> | |
34 | #include "SecCFWrappers.h" | |
35 | #include "SecCFError.h" | |
36 | #include "SecIOFormat.h" | |
37 | #include <stdio.h> | |
38 | #include "Security/SecBase.h" | |
d8f41ccd A |
39 | #include <SecureObjectSync/SOSDigestVector.h> |
40 | #include <SecureObjectSync/SOSManifest.h> | |
427c49bc A |
41 | |
42 | #define LOGE(ARG,...) secerror(ARG, ## __VA_ARGS__) | |
43 | #define LOGV(ARG,...) secdebug("secdb", ARG, ## __VA_ARGS__) | |
44 | #define LOGD(ARG,...) secdebug("secdb", ARG, ## __VA_ARGS__) | |
45 | ||
46 | #define HAVE_UNLOCK_NOTIFY 0 | |
47 | #define USE_BUSY_HANDLER 1 | |
48 | ||
49 | struct __OpaqueSecDbStatement { | |
50 | CFRuntimeBase _base; | |
51 | ||
52 | SecDbConnectionRef dbconn; | |
53 | sqlite3_stmt *stmt; | |
54 | }; | |
55 | ||
56 | struct __OpaqueSecDbConnection { | |
57 | CFRuntimeBase _base; | |
58 | ||
59 | //CFMutableDictionaryRef statements; | |
60 | ||
61 | SecDbRef db; // NONRETAINED, since db or block retains us | |
62 | bool readOnly; | |
63 | bool inTransaction; | |
d8f41ccd | 64 | SecDbTransactionSource source; |
427c49bc A |
65 | bool isCorrupted; |
66 | sqlite3 *handle; | |
d8f41ccd A |
67 | // Pending deletions and addtions to the manifest for the current transaction |
68 | struct SOSDigestVector todel; | |
69 | struct SOSDigestVector toadd; | |
427c49bc A |
70 | }; |
71 | ||
72 | struct __OpaqueSecDb { | |
73 | CFRuntimeBase _base; | |
74 | ||
75 | CFStringRef db_path; | |
76 | dispatch_queue_t queue; | |
77 | CFMutableArrayRef connections; | |
78 | dispatch_semaphore_t write_semaphore; | |
79 | dispatch_semaphore_t read_semaphore; | |
80 | bool didFirstOpen; | |
81 | bool (^opened)(SecDbConnectionRef dbconn, bool did_create, CFErrorRef *error); | |
60c433a9 | 82 | dispatch_queue_t notifyQueue; |
d8f41ccd | 83 | SecDBNotifyBlock notifyPhase; |
427c49bc A |
84 | }; |
85 | ||
86 | // MARK: Error domains and error helper functions | |
87 | ||
88 | CFStringRef kSecDbErrorDomain = CFSTR("com.apple.utilities.sqlite3"); | |
89 | ||
90 | bool SecDbError(int sql_code, CFErrorRef *error, CFStringRef format, ...) { | |
91 | if (sql_code == SQLITE_OK) return true; | |
92 | if (error) { | |
93 | va_list args; | |
94 | CFIndex code = sql_code; | |
95 | CFErrorRef previousError = *error; | |
96 | ||
97 | *error = NULL; | |
98 | va_start(args, format); | |
99 | SecCFCreateErrorWithFormatAndArguments(code, kSecDbErrorDomain, previousError, error, NULL, format, args); | |
d8f41ccd | 100 | CFReleaseNull(previousError); |
427c49bc A |
101 | va_end(args); |
102 | } | |
103 | return false; | |
104 | } | |
105 | ||
106 | bool SecDbErrorWithDb(int sql_code, sqlite3 *db, CFErrorRef *error, CFStringRef format, ...) { | |
107 | if (sql_code == SQLITE_OK) return true; | |
108 | if (error) { | |
109 | va_list args; | |
110 | va_start(args, format); | |
111 | CFStringRef message = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, format, args); | |
112 | va_end(args); | |
113 | ||
114 | int extended_code = sqlite3_extended_errcode(db); | |
115 | if (sql_code == extended_code) | |
116 | SecDbError(sql_code, error, CFSTR("%@: [%d] %s"), message, sql_code, sqlite3_errmsg(db)); | |
117 | else | |
118 | SecDbError(sql_code, error, CFSTR("%@: [%d->%d] %s"), message, sql_code, extended_code, sqlite3_errmsg(db)); | |
119 | CFReleaseSafe(message); | |
120 | } | |
121 | return false; | |
122 | } | |
123 | ||
124 | bool SecDbErrorWithStmt(int sql_code, sqlite3_stmt *stmt, CFErrorRef *error, CFStringRef format, ...) { | |
125 | if (sql_code == SQLITE_OK) return true; | |
126 | if (error) { | |
127 | va_list args; | |
128 | va_start(args, format); | |
129 | CFStringRef message = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, format, args); | |
130 | va_end(args); | |
131 | ||
132 | sqlite3 *db = sqlite3_db_handle(stmt); | |
133 | const char *sql = sqlite3_sql(stmt); | |
134 | int extended_code = sqlite3_extended_errcode(db); | |
135 | if (sql_code == extended_code) | |
136 | SecDbError(sql_code, error, CFSTR("%@: [%d] %s sql: %s"), message, sql_code, sqlite3_errmsg(db), sql); | |
137 | else | |
138 | SecDbError(sql_code, error, CFSTR("%@: [%d->%d] %s sql: %s"), message, sql_code, extended_code, sqlite3_errmsg(db), sql); | |
139 | CFReleaseSafe(message); | |
140 | } | |
141 | return false; | |
142 | } | |
143 | ||
144 | ||
145 | // MARK: - | |
146 | // MARK: Static helper functions | |
147 | ||
148 | static bool SecDbOpenHandle(SecDbConnectionRef dbconn, bool *created, CFErrorRef *error); | |
149 | static bool SecDbHandleCorrupt(SecDbConnectionRef dbconn, int rc, CFErrorRef *error); | |
150 | ||
151 | #pragma mark - | |
152 | #pragma mark SecDbRef | |
153 | ||
154 | static CFStringRef | |
d87e1158 | 155 | SecDbCopyFormatDescription(CFTypeRef value, CFDictionaryRef formatOptions) |
427c49bc A |
156 | { |
157 | SecDbRef db = (SecDbRef)value; | |
158 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<SecDb path:%@ connections: %@>"), db->db_path, db->connections); | |
159 | } | |
160 | ||
161 | ||
162 | static void | |
163 | SecDbDestroy(CFTypeRef value) | |
164 | { | |
165 | SecDbRef db = (SecDbRef)value; | |
166 | CFReleaseSafe(db->connections); | |
167 | CFReleaseSafe(db->db_path); | |
168 | dispatch_release(db->queue); | |
169 | dispatch_release(db->read_semaphore); | |
170 | dispatch_release(db->write_semaphore); | |
171 | } | |
172 | ||
173 | CFGiblisFor(SecDb) | |
174 | ||
175 | SecDbRef | |
176 | SecDbCreate(CFStringRef dbName, | |
177 | bool (^opened)(SecDbConnectionRef dbconn, bool did_create, CFErrorRef *error)) | |
178 | { | |
179 | SecDbRef db = NULL; | |
180 | ||
181 | db = CFTypeAllocate(SecDb, struct __OpaqueSecDb, kCFAllocatorDefault); | |
182 | require(db != NULL, done); | |
183 | ||
184 | CFStringPerformWithCString(dbName, ^(const char *dbNameStr) { | |
185 | db->queue = dispatch_queue_create(dbNameStr, DISPATCH_QUEUE_SERIAL); | |
186 | }); | |
187 | db->read_semaphore = dispatch_semaphore_create(kSecDbMaxReaders); | |
188 | db->write_semaphore = dispatch_semaphore_create(kSecDbMaxWriters); | |
189 | db->connections = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
190 | db->opened = opened; | |
191 | if (getenv("__OSINSTALL_ENVIRONMENT") != NULL) { | |
192 | // TODO: Move this code out of this layer | |
193 | LOGV("sqlDb: running from installer"); | |
194 | db->db_path = CFSTR("file::memory:?cache=shared"); | |
195 | } else { | |
196 | db->db_path = CFStringCreateCopy(kCFAllocatorDefault, dbName); | |
197 | } | |
198 | done: | |
199 | return db; | |
200 | } | |
201 | ||
202 | CFIndex | |
203 | SecDbIdleConnectionCount(SecDbRef db) { | |
204 | __block CFIndex count = 0; | |
205 | dispatch_sync(db->queue, ^{ | |
206 | count = CFArrayGetCount(db->connections); | |
207 | }); | |
208 | return count; | |
209 | } | |
210 | ||
60c433a9 A |
211 | void SecDbSetNotifyPhaseBlock(SecDbRef db, dispatch_queue_t queue, SecDBNotifyBlock notifyPhase) { |
212 | if (db->notifyQueue) | |
213 | dispatch_release(db->notifyQueue); | |
d8f41ccd A |
214 | if (db->notifyPhase) |
215 | Block_release(db->notifyPhase); | |
60c433a9 A |
216 | |
217 | if (queue) { | |
218 | db->notifyQueue = queue; | |
219 | dispatch_retain(db->notifyQueue); | |
220 | } else { | |
221 | db->notifyQueue = NULL; | |
222 | } | |
223 | if (notifyPhase) | |
224 | db->notifyPhase = Block_copy(notifyPhase); | |
225 | else | |
226 | db->notifyPhase = NULL; | |
d8f41ccd A |
227 | } |
228 | ||
229 | static void SecDbNotifyPhase(SecDbConnectionRef dbconn, SecDbTransactionPhase phase) { | |
230 | if (dbconn->todel.count || dbconn->toadd.count) { | |
231 | if (dbconn->db->notifyPhase) | |
232 | dbconn->db->notifyPhase(dbconn, phase, dbconn->source, &dbconn->todel, &dbconn->toadd); | |
233 | SOSDigestVectorFree(&dbconn->todel); | |
234 | SOSDigestVectorFree(&dbconn->toadd); | |
235 | } | |
236 | } | |
237 | ||
60c433a9 A |
238 | static bool SecDbOnNotifyQueue(SecDbConnectionRef dbconn, bool (^perform)()) { |
239 | __block bool result = false; | |
240 | ||
241 | if (dbconn->db->notifyPhase) { | |
242 | dispatch_sync(dbconn->db->notifyQueue, ^{ | |
243 | result = perform(); | |
244 | }); | |
245 | } else { | |
246 | result = perform(); | |
247 | } | |
248 | ||
249 | return result; | |
250 | } | |
251 | ||
d8f41ccd A |
252 | CFStringRef SecDbGetPath(SecDbRef db) { |
253 | return db->db_path; | |
254 | } | |
255 | ||
427c49bc A |
256 | |
257 | #pragma mark - | |
258 | #pragma mark SecDbConnectionRef | |
259 | ||
260 | static bool SecDbCheckCorrupted(SecDbConnectionRef dbconn) | |
261 | { | |
262 | __block bool isCorrupted = true; | |
263 | __block CFErrorRef error = NULL; | |
264 | SecDbPrepare(dbconn, CFSTR("PRAGMA integrity_check"), &error, ^(sqlite3_stmt *stmt) { | |
265 | SecDbStep(dbconn, stmt, &error, ^(bool *stop) { | |
266 | const char * result = (const char*)sqlite3_column_text(stmt, 0); | |
267 | if (result && strncasecmp(result, "ok", 3) == 0) { | |
268 | isCorrupted = false; | |
269 | } | |
270 | }); | |
271 | }); | |
272 | if (error) { | |
273 | LOGV("sqlDb: warning error %@ when running integrity check", error); | |
274 | CFRelease(error); | |
275 | } | |
276 | return isCorrupted; | |
277 | } | |
278 | ||
279 | static bool SecDbDidCreateFirstConnection(SecDbConnectionRef dbconn, bool didCreate, CFErrorRef *error) | |
280 | { | |
281 | LOGD("sqlDb: starting maintenance"); | |
282 | bool ok = true; | |
283 | ||
284 | if (!didCreate && !dbconn->isCorrupted) { | |
285 | dbconn->isCorrupted = SecDbCheckCorrupted(dbconn); | |
286 | if (dbconn->isCorrupted) | |
287 | secerror("integrity check=fail"); | |
288 | else | |
289 | LOGD("sqlDb: integrity check=pass"); | |
290 | } | |
291 | ||
292 | if (!dbconn->isCorrupted && dbconn->db->opened) { | |
293 | CFErrorRef localError = NULL; | |
294 | ||
295 | ok = dbconn->db->opened(dbconn, didCreate, &localError); | |
296 | ||
297 | if (!ok) | |
298 | secerror("opened block failed: %@", localError); | |
299 | ||
300 | if (!dbconn->isCorrupted && error && *error == NULL) { | |
301 | *error = localError; | |
302 | localError = NULL; | |
303 | } else { | |
304 | secerror("opened block failed: error is released and lost"); | |
305 | CFReleaseNull(localError); | |
306 | } | |
307 | } | |
308 | ||
309 | if (dbconn->isCorrupted) { | |
310 | ok = SecDbHandleCorrupt(dbconn, 0, error); | |
311 | } | |
312 | ||
313 | LOGD("sqlDb: finished maintenance"); | |
314 | return ok; | |
315 | } | |
316 | ||
317 | void SecDbCorrupt(SecDbConnectionRef dbconn) | |
318 | { | |
319 | dbconn->isCorrupted = true; | |
320 | } | |
321 | ||
322 | ||
323 | static uint8_t knownDbPathIndex(SecDbConnectionRef dbconn) | |
324 | { | |
325 | ||
326 | if(CFEqual(dbconn->db->db_path, CFSTR("/Library/Keychains/keychain-2.db"))) | |
327 | return 1; | |
328 | if(CFEqual(dbconn->db->db_path, CFSTR("/Library/Keychains/ocspcache.sqlite3"))) | |
329 | return 2; | |
330 | if(CFEqual(dbconn->db->db_path, CFSTR("/Library/Keychains/TrustStore.sqlite3"))) | |
331 | return 3; | |
332 | if(CFEqual(dbconn->db->db_path, CFSTR("/Library/Keychains/caissuercache.sqlite3"))) | |
333 | return 4; | |
334 | ||
335 | /* Unknown DB path */ | |
336 | return 0; | |
337 | } | |
338 | ||
339 | ||
340 | // Return true if there was no error, returns false otherwise and set *error to an appropriate CFErrorRef. | |
341 | static bool SecDbConnectionCheckCode(SecDbConnectionRef dbconn, int code, CFErrorRef *error, CFStringRef desc, ...) { | |
342 | if (code == SQLITE_OK || code == SQLITE_DONE) | |
343 | return true; | |
344 | ||
345 | if (error) { | |
346 | va_list args; | |
347 | va_start(args, desc); | |
348 | CFStringRef msg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, desc, args); | |
349 | va_end(args); | |
350 | SecDbErrorWithDb(code, dbconn->handle, error, msg); | |
351 | CFRelease(msg); | |
352 | } | |
353 | ||
354 | /* If it's already corrupted, don't try to recover */ | |
355 | if (dbconn->isCorrupted) { | |
356 | CFStringRef reason = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("SQL DB %@ is corrupted already. Not trying to recover"), dbconn->db->db_path); | |
357 | secerror("%@",reason); | |
358 | __security_simulatecrash(reason, __sec_exception_code_TwiceCorruptDb(knownDbPathIndex(dbconn))); | |
359 | CFReleaseSafe(reason); | |
360 | return false; | |
361 | } | |
362 | ||
363 | dbconn->isCorrupted = (SQLITE_CORRUPT == code) || (SQLITE_NOTADB == code) || (SQLITE_IOERR == code) || (SQLITE_CANTOPEN == code); | |
364 | if (dbconn->isCorrupted) { | |
365 | /* Run integrity check and only make dbconn->isCorrupted true and | |
366 | run the corruption handler if the integrity check conclusively fails. */ | |
367 | dbconn->isCorrupted = SecDbCheckCorrupted(dbconn); | |
368 | if (dbconn->isCorrupted) { | |
369 | secerror("operation returned code: %d integrity check=fail", code); | |
370 | SecDbHandleCorrupt(dbconn, code, error); | |
371 | } else { | |
372 | secerror("operation returned code: %d: integrity check=pass", code); | |
373 | } | |
374 | } | |
375 | ||
376 | return false; | |
377 | } | |
378 | ||
379 | #if HAVE_UNLOCK_NOTIFY | |
380 | ||
381 | static void SecDbUnlockNotify(void **apArg, int nArg) { | |
382 | int i; | |
383 | for(i=0; i<nArg; i++) { | |
384 | dispatch_semaphore_t dsema = (dispatch_semaphore_t)apArg[i]; | |
385 | dispatch_semaphore_signal(dsema); | |
386 | } | |
387 | } | |
388 | ||
389 | static bool SecDbWaitForUnlockNotify(SecDbConnectionRef dbconn, sqlite3_stmt *stmt, CFErrorRef *error) { | |
390 | int rc; | |
391 | dispatch_semaphore_t dsema = dispatch_semaphore_create(0); | |
392 | rc = sqlite3_unlock_notify(dbconn->handle, SecDbUnlockNotify, dsema); | |
393 | assert(rc == SQLITE_LOCKED || rc == SQLITE_OK); | |
394 | if (rc == SQLITE_OK) { | |
395 | dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER); | |
396 | } | |
397 | dispatch_release(dsema); | |
398 | return (rc == SQLITE_OK | |
399 | ? true | |
400 | : (stmt | |
401 | ? SecDbErrorWithStmt(rc, stmt, error, CFSTR("sqlite3_unlock_notify")) | |
402 | : SecDbErrorWithDb(rc, dbconn->handle, error, CFSTR("sqlite3_unlock_notify")))); | |
403 | } | |
404 | ||
405 | #endif | |
406 | ||
407 | #if USE_BUSY_HANDLER | |
408 | ||
409 | // Return 0 to stop retrying. | |
410 | static int SecDbHandleBusy(void *ctx, int retryCount) { | |
411 | SecDbConnectionRef dbconn __unused = ctx; | |
412 | struct timespec sleeptime = { .tv_sec = 0, .tv_nsec = 10000 }; | |
413 | while (retryCount--) { | |
414 | // Double sleeptime until we hit one second then add one | |
415 | // second more every time we sleep. | |
416 | if (sleeptime.tv_sec) { | |
417 | sleeptime.tv_sec++; | |
418 | } else { | |
419 | sleeptime.tv_nsec *= 2; | |
420 | if (sleeptime.tv_nsec > NSEC_PER_SEC) { | |
421 | sleeptime.tv_nsec = 0; | |
422 | sleeptime.tv_sec++; | |
423 | } | |
424 | } | |
425 | } | |
426 | struct timespec unslept = {}; | |
427 | nanosleep(&sleeptime, &unslept); | |
428 | ||
429 | return 1; | |
430 | } | |
431 | ||
432 | static bool SecDbBusyHandler(SecDbConnectionRef dbconn, CFErrorRef *error) { | |
433 | return SecDbErrorWithDb(sqlite3_busy_handler(dbconn->handle, SecDbHandleBusy, dbconn), dbconn->handle, error, CFSTR("busy_handler")); | |
434 | } | |
435 | ||
436 | #endif // USE_BUSY_HANDLER | |
437 | ||
438 | // Return true causes the operation to be tried again. | |
439 | static bool SecDbWaitIfNeeded(SecDbConnectionRef dbconn, int s3e, sqlite3_stmt *stmt, CFStringRef desc, struct timespec *sleeptime, CFErrorRef *error) { | |
440 | #if HAVE_UNLOCK_NOTIFY | |
441 | if (s3e == SQLITE_LOCKED) { // Optionally check for extended code being SQLITE_LOCKED_SHAREDCACHE | |
442 | return SecDbWaitForUnlockNotify(dbconn, stmt, error)) | |
443 | } | |
444 | #endif | |
445 | ||
446 | #if !USE_BUSY_HANDLER | |
447 | if (s3e == SQLITE_LOCKED || s3e == SQLITE_BUSY) { | |
448 | LOGV("sqlDb: %s", sqlite3_errmsg(dbconn->handle)); | |
449 | while (s3e == SQLITE_LOCKED || s3e == SQLITE_BUSY) { | |
450 | struct timespec unslept = {}; | |
451 | nanosleep(sleeptime, &unslept); | |
452 | s3e = SQLITE_OK; | |
453 | if (stmt) | |
454 | s3e = sqlite3_reset(stmt); | |
455 | ||
456 | // Double sleeptime until we hit one second the add one | |
457 | // second more every time we sleep. | |
458 | if (sleeptime->tv_sec) { | |
459 | sleeptime->tv_sec++; | |
460 | } else { | |
461 | sleeptime->tv_nsec *= 2; | |
462 | if (sleeptime->tv_nsec > NSEC_PER_SEC) { | |
463 | sleeptime->tv_nsec = 0; | |
464 | sleeptime->tv_sec++; | |
465 | } | |
466 | } | |
467 | } | |
468 | if (s3e) | |
469 | return SecDbErrorWithStmt(s3e, stmt, error, CFSTR("reset")); | |
470 | } else | |
471 | #endif // !USE_BUSY_HANDLER | |
472 | { | |
473 | return SecDbConnectionCheckCode(dbconn, s3e, error, desc); | |
474 | } | |
475 | return true; | |
476 | } | |
477 | ||
478 | enum SecDbStepResult { | |
479 | kSecDbErrorStep = 0, | |
480 | kSecDbRowStep = 1, | |
481 | kSecDbDoneStep = 2, | |
482 | }; | |
483 | typedef enum SecDbStepResult SecDbStepResult; | |
484 | ||
485 | static SecDbStepResult _SecDbStep(SecDbConnectionRef dbconn, sqlite3_stmt *stmt, CFErrorRef *error) { | |
486 | assert(stmt != NULL); | |
487 | int s3e; | |
488 | struct timespec sleeptime = { .tv_sec = 0, .tv_nsec = 10000 }; | |
489 | for (;;) { | |
490 | s3e = sqlite3_step(stmt); | |
491 | if (s3e == SQLITE_ROW) | |
492 | return kSecDbRowStep; | |
493 | else if (s3e == SQLITE_DONE) | |
494 | return kSecDbDoneStep; | |
495 | else if (!SecDbWaitIfNeeded(dbconn, s3e, stmt, CFSTR("step"), &sleeptime, error)) | |
496 | return kSecDbErrorStep; | |
497 | }; | |
498 | } | |
499 | ||
500 | bool | |
501 | SecDbExec(SecDbConnectionRef dbconn, CFStringRef sql, CFErrorRef *error) | |
502 | { | |
503 | bool ok = true; | |
504 | CFRetain(sql); | |
505 | while (sql) { | |
506 | CFStringRef tail = NULL; | |
507 | if (ok) { | |
508 | sqlite3_stmt *stmt = SecDbCopyStmt(dbconn, sql, &tail, error); | |
509 | ok = stmt != NULL; | |
510 | if (stmt) { | |
511 | SecDbStepResult sr; | |
512 | while ((sr = _SecDbStep(dbconn, stmt, error)) == kSecDbRowStep); | |
513 | if (sr == kSecDbErrorStep) | |
514 | ok = false; | |
515 | ok &= SecDbReleaseCachedStmt(dbconn, sql, stmt, error); | |
516 | } | |
517 | } else { | |
518 | // TODO We already have an error here we really just want the left over sql in it's userData | |
519 | ok = SecDbError(SQLITE_ERROR, error, CFSTR("Error with unexecuted sql remaining %@"), sql); | |
520 | } | |
521 | CFRelease(sql); | |
522 | sql = tail; | |
523 | } | |
524 | return ok; | |
525 | } | |
526 | ||
527 | static bool SecDbBeginTransaction(SecDbConnectionRef dbconn, SecDbTransactionType type, CFErrorRef *error) | |
528 | { | |
529 | bool ok = true; | |
530 | CFStringRef query; | |
531 | switch (type) { | |
532 | case kSecDbImmediateTransactionType: | |
533 | query = CFSTR("BEGIN IMMEDATE"); | |
534 | break; | |
d8f41ccd A |
535 | case kSecDbExclusiveRemoteTransactionType: |
536 | dbconn->source = kSecDbSOSTransaction; | |
427c49bc A |
537 | case kSecDbExclusiveTransactionType: |
538 | query = CFSTR("BEGIN EXCLUSIVE"); | |
539 | break; | |
540 | case kSecDbNormalTransactionType: | |
541 | query = CFSTR("BEGIN"); | |
542 | break; | |
543 | default: | |
544 | ok = SecDbError(SQLITE_ERROR, error, CFSTR("invalid transaction type %" PRIu32), type); | |
545 | query = NULL; | |
546 | break; | |
547 | } | |
548 | ||
549 | if (query != NULL && sqlite3_get_autocommit(dbconn->handle) != 0) { | |
550 | ok = SecDbExec(dbconn, query, error); | |
551 | } | |
d8f41ccd A |
552 | if (ok) |
553 | dbconn->inTransaction = true; | |
427c49bc A |
554 | |
555 | return ok; | |
556 | } | |
557 | ||
558 | static bool SecDbEndTransaction(SecDbConnectionRef dbconn, bool commit, CFErrorRef *error) | |
559 | { | |
60c433a9 A |
560 | return SecDbOnNotifyQueue(dbconn, ^{ |
561 | bool ok = true, commited = false; | |
562 | if (commit) { | |
563 | SecDbNotifyPhase(dbconn, kSecDbTransactionWillCommit); | |
564 | commited = ok = SecDbExec(dbconn, CFSTR("END"), error); | |
565 | } else { | |
566 | ok = SecDbExec(dbconn, CFSTR("ROLLBACK"), error); | |
567 | commited = false; | |
568 | } | |
569 | dbconn->inTransaction = false; | |
570 | SecDbNotifyPhase(dbconn, commited ? kSecDbTransactionDidCommit : kSecDbTransactionDidRollback); | |
571 | dbconn->source = kSecDbAPITransaction; | |
572 | return ok; | |
573 | }); | |
427c49bc A |
574 | } |
575 | ||
576 | bool SecDbTransaction(SecDbConnectionRef dbconn, SecDbTransactionType type, | |
577 | CFErrorRef *error, void (^transaction)(bool *commit)) | |
578 | { | |
579 | bool ok = true; | |
580 | bool commit = true; | |
581 | ||
582 | if (dbconn->inTransaction) { | |
583 | transaction(&commit); | |
584 | if (!commit) { | |
585 | LOGV("sqlDb: nested transaction asked to not be committed"); | |
586 | } | |
587 | } else { | |
588 | ok = SecDbBeginTransaction(dbconn, type, error); | |
589 | if (ok) { | |
427c49bc | 590 | transaction(&commit); |
427c49bc A |
591 | ok = SecDbEndTransaction(dbconn, commit, error); |
592 | } | |
593 | } | |
594 | ||
595 | done: | |
596 | return ok && commit; | |
597 | } | |
598 | ||
599 | sqlite3 *SecDbHandle(SecDbConnectionRef dbconn) { | |
600 | return dbconn->handle; | |
601 | } | |
602 | ||
603 | bool SecDbStep(SecDbConnectionRef dbconn, sqlite3_stmt *stmt, CFErrorRef *error, void (^row)(bool *stop)) { | |
604 | for (;;) { | |
605 | switch (_SecDbStep(dbconn, stmt, error)) { | |
606 | case kSecDbErrorStep: | |
607 | return false; | |
608 | case kSecDbRowStep: | |
609 | if (row) { | |
610 | bool stop = false; | |
611 | row(&stop); | |
612 | if (stop) | |
613 | return true; | |
614 | break; | |
615 | } | |
616 | SecDbError(SQLITE_ERROR, error, CFSTR("SecDbStep SQLITE_ROW returned without a row handler")); | |
617 | return false; | |
618 | case kSecDbDoneStep: | |
619 | return true; | |
620 | } | |
621 | } | |
622 | } | |
623 | ||
624 | bool SecDbCheckpoint(SecDbConnectionRef dbconn, CFErrorRef *error) | |
625 | { | |
626 | return SecDbConnectionCheckCode(dbconn, sqlite3_wal_checkpoint(dbconn->handle, NULL), error, CFSTR("wal_checkpoint")); | |
627 | } | |
628 | ||
629 | static bool SecDbFileControl(SecDbConnectionRef dbconn, int op, void *arg, CFErrorRef *error) { | |
630 | return SecDbConnectionCheckCode(dbconn, sqlite3_file_control(dbconn->handle, NULL, op, arg), error, CFSTR("file_control")); | |
631 | } | |
632 | ||
633 | static sqlite3 *_SecDbOpenV2(const char *path, int flags, CFErrorRef *error) { | |
634 | #if HAVE_UNLOCK_NOTIFY | |
635 | flags |= SQLITE_OPEN_SHAREDCACHE; | |
636 | #endif | |
637 | sqlite3 *handle = NULL; | |
638 | int s3e = sqlite3_open_v2(path, &handle, flags, NULL); | |
639 | if (s3e) { | |
640 | if (handle) { | |
641 | SecDbErrorWithDb(s3e, handle, error, CFSTR("open_v2 \"%s\" 0x%X"), path, flags); | |
642 | sqlite3_close(handle); | |
643 | handle = NULL; | |
644 | } else { | |
645 | SecDbError(s3e, error, CFSTR("open_v2 \"%s\" 0x%X"), path, flags); | |
646 | } | |
647 | } | |
648 | return handle; | |
649 | } | |
650 | ||
651 | static bool SecDbOpenV2(SecDbConnectionRef dbconn, const char *path, int flags, CFErrorRef *error) { | |
652 | return (dbconn->handle = _SecDbOpenV2(path, flags, error)) != NULL; | |
653 | } | |
654 | ||
655 | static bool SecDbTruncate(SecDbConnectionRef dbconn, CFErrorRef *error) | |
656 | { | |
657 | int flags = SQLITE_TRUNCATE_JOURNALMODE_WAL | SQLITE_TRUNCATE_AUTOVACUUM_FULL; | |
658 | __block bool ok = SecDbFileControl(dbconn, SQLITE_TRUNCATE_DATABASE, &flags, error); | |
659 | if (!ok) { | |
660 | sqlite3_close(dbconn->handle); | |
661 | dbconn->handle = NULL; | |
662 | CFStringPerformWithCString(dbconn->db->db_path, ^(const char *path) { | |
663 | if (error) | |
664 | CFReleaseNull(*error); | |
665 | if (SecCheckErrno(unlink(path), error, CFSTR("unlink %s"), path)) { | |
666 | ok = SecDbOpenHandle(dbconn, NULL, error); | |
667 | } | |
668 | }); | |
669 | if (!ok) { | |
670 | secerror("Failed to delete db handle: %@", error ? *error : NULL); | |
671 | abort(); | |
672 | } | |
673 | } | |
674 | ||
675 | return ok; | |
676 | } | |
677 | ||
678 | static bool SecDbHandleCorrupt(SecDbConnectionRef dbconn, int rc, CFErrorRef *error) | |
679 | { | |
680 | CFStringRef reason = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("SQL DB %@ is corrupted, trying to recover (rc=%d)"), dbconn->db->db_path, rc); | |
681 | __security_simulatecrash(reason, __sec_exception_code_CorruptDb(knownDbPathIndex(dbconn), rc)); | |
682 | CFReleaseSafe(reason); | |
683 | ||
684 | // Backup current db. | |
685 | __block bool didRename = false; | |
686 | CFStringPerformWithCString(dbconn->db->db_path, ^(const char *db_path) { | |
687 | sqlite3 *corrupt_db = NULL; | |
688 | char buf[PATH_MAX+1]; | |
689 | snprintf(buf, sizeof(buf), "%s-corrupt", db_path); | |
690 | if (dbconn->handle && (corrupt_db = _SecDbOpenV2(buf, SQLITE_OPEN_READWRITE, error))) { | |
691 | int on = 1; | |
692 | didRename = SecDbErrorWithDb(sqlite3_file_control(corrupt_db, NULL, SQLITE_FCNTL_PERSIST_WAL, &on), corrupt_db, error, CFSTR("persist wal")); | |
693 | didRename &= SecDbErrorWithDb(sqlite3_file_control(corrupt_db, NULL, SQLITE_REPLACE_DATABASE, (void *)dbconn->handle), corrupt_db, error, CFSTR("replace database")); | |
694 | sqlite3_close(corrupt_db); | |
695 | } | |
696 | if (!didRename) { | |
697 | if (dbconn->handle) | |
698 | secerror("Tried to rename corrupt database at path %@, but we failed: %@, trying explicit rename", dbconn->db->db_path, error ? *error : NULL); | |
699 | if (error) | |
700 | CFReleaseNull(*error); | |
701 | ||
702 | didRename = SecCheckErrno(rename(db_path, buf), error, CFSTR("rename %s %s"), db_path, buf) && | |
703 | (!dbconn->handle || SecDbError(sqlite3_close(dbconn->handle), error, CFSTR("close"))) && | |
704 | SecDbOpenHandle(dbconn, NULL, error); | |
705 | } | |
706 | if (didRename) { | |
707 | secerror("Database at path %@ is corrupt. Copied it to %s for further investigation.", dbconn->db->db_path, buf); | |
708 | } else { | |
709 | seccritical("Tried to copy corrupt database at path %@, but we failed: %@", dbconn->db->db_path, error ? *error : NULL); | |
710 | } | |
711 | }); | |
712 | ||
713 | bool ok = (didRename && | |
714 | (dbconn->handle || SecDbOpenHandle(dbconn, NULL, error)) && | |
715 | SecDbTruncate(dbconn, error)); | |
716 | ||
717 | // Mark the db as not corrupted, even if something failed. | |
718 | // Always note we are no longer in the corruption handler | |
719 | dbconn->isCorrupted = false; | |
720 | ||
721 | // Invoke our callers opened callback, since we just created a new database | |
722 | if (ok && dbconn->db->opened) | |
723 | ok = dbconn->db->opened(dbconn, true, error); | |
724 | ||
725 | return ok; | |
726 | } | |
727 | ||
728 | static bool SecDbProfileEnabled(void) | |
729 | { | |
730 | #if 0 | |
731 | static dispatch_once_t onceToken; | |
732 | static bool profile_enabled = false; | |
733 | ||
734 | #if DEBUG | |
735 | //sudo defaults write /Library/Preferences/com.apple.security.auth profile -bool true | |
736 | dispatch_once(&onceToken, ^{ | |
737 | CFTypeRef profile = (CFNumberRef)CFPreferencesCopyValue(CFSTR("profile"), CFSTR(SECURITY_AUTH_NAME), kCFPreferencesAnyUser, kCFPreferencesCurrentHost); | |
738 | ||
739 | if (profile && CFGetTypeID(profile) == CFBooleanGetTypeID()) { | |
740 | profile_enabled = CFBooleanGetValue((CFBooleanRef)profile); | |
741 | } | |
742 | ||
743 | LOGV("sqlDb: sql profile: %s", profile_enabled ? "enabled" : "disabled"); | |
744 | ||
745 | CFReleaseSafe(profile); | |
746 | }); | |
747 | #endif | |
748 | ||
749 | return profile_enabled; | |
750 | #else | |
751 | #if DEBUG | |
752 | return true; | |
753 | #else | |
754 | return false; | |
755 | #endif | |
756 | #endif | |
757 | } | |
758 | ||
759 | #if 0 | |
760 | static void SecDbProfile(void *context __unused, const char *sql, sqlite3_uint64 ns) { | |
761 | LOGV("==\nsqlDb: %s\nTime: %llu ms\n", sql, ns >> 20); | |
762 | } | |
763 | #else | |
764 | static void SecDbProfile(void *context, const char *sql, sqlite3_uint64 ns) { | |
765 | sqlite3 *s3h = context; | |
766 | int code = sqlite3_extended_errcode(s3h); | |
767 | if (code == SQLITE_OK || code == SQLITE_DONE) { | |
768 | secdebug("profile", "==\nsqlDb: %s\nTime: %llu ms\n", sql, ns >> 20); | |
769 | } else { | |
770 | secdebug("profile", "==error[%d]: %s==\nsqlDb: %s\nTime: %llu ms \n", code, sqlite3_errmsg(s3h), sql, ns >> 20); | |
771 | } | |
772 | } | |
773 | #endif | |
774 | ||
775 | static bool SecDbTraceEnabled(void) | |
776 | { | |
777 | #if DEBUG | |
778 | return true; | |
779 | #else | |
780 | return false; | |
781 | #endif | |
782 | } | |
783 | ||
784 | static void SecDbTrace(void *ctx, const char *trace) { | |
785 | SecDbConnectionRef dbconn __unused = ctx; | |
786 | static dispatch_queue_t queue; | |
787 | static dispatch_once_t once; | |
788 | dispatch_once(&once, ^{ | |
789 | queue = dispatch_queue_create("trace_queue", DISPATCH_QUEUE_SERIAL); | |
790 | }); | |
791 | dispatch_sync(queue, ^{ | |
792 | __security_debug(CFSTR("trace"), "", "", 0, CFSTR("%s"), trace); | |
793 | }); | |
794 | } | |
795 | ||
796 | static bool SecDbOpenHandle(SecDbConnectionRef dbconn, bool *created, CFErrorRef *error) | |
797 | { | |
798 | __block bool ok = true; | |
799 | CFStringPerformWithCString(dbconn->db->db_path, ^(const char *db_path) { | |
800 | ok = created && SecDbOpenV2(dbconn, db_path, SQLITE_OPEN_READWRITE, NULL); | |
801 | if (!ok) { | |
802 | ok = true; | |
803 | if (created) { | |
804 | char *tmp = dirname((char *)db_path); | |
805 | if (tmp) { | |
806 | int errnum = mkpath_np(tmp, 0700); | |
807 | if (errnum != 0 && errnum != EEXIST) { | |
808 | SecCFCreateErrorWithFormat(errnum, kSecErrnoDomain, NULL, error, NULL, | |
809 | CFSTR("mkpath_np %s: [%d] %s"), tmp, errnum, strerror(errnum)); | |
810 | ok = false; | |
811 | } | |
812 | } | |
813 | } | |
814 | ok = ok && SecDbOpenV2(dbconn, db_path, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, error); | |
815 | if (ok) { | |
816 | chmod(db_path, S_IRUSR | S_IWUSR); | |
817 | if (created) | |
818 | *created = true; | |
819 | } | |
820 | } | |
821 | ||
822 | if (ok && SecDbProfileEnabled()) { | |
823 | sqlite3_profile(dbconn->handle, SecDbProfile, dbconn->handle); | |
824 | } | |
825 | if (ok && SecDbTraceEnabled()) { | |
826 | sqlite3_trace(dbconn->handle, SecDbTrace, dbconn); | |
827 | } | |
828 | #if USE_BUSY_HANDLER | |
829 | ok = ok && SecDbBusyHandler(dbconn, error); | |
830 | #endif | |
831 | }); | |
832 | ||
833 | done: | |
834 | return ok; | |
835 | } | |
836 | ||
837 | static SecDbConnectionRef | |
838 | SecDbConnectionCreate(SecDbRef db, bool readOnly, CFErrorRef *error) | |
839 | { | |
840 | SecDbConnectionRef dbconn = NULL; | |
841 | ||
842 | dbconn = CFTypeAllocate(SecDbConnection, struct __OpaqueSecDbConnection, kCFAllocatorDefault); | |
843 | require(dbconn != NULL, done); | |
844 | ||
845 | dbconn->db = db; | |
846 | dbconn->readOnly = readOnly; | |
847 | ||
848 | done: | |
849 | return dbconn; | |
850 | } | |
851 | ||
852 | static bool SecDbConnectionIsReadOnly(SecDbConnectionRef dbconn) { | |
853 | return dbconn->readOnly; | |
854 | } | |
855 | ||
856 | static void SecDbConectionSetReadOnly(SecDbConnectionRef dbconn, bool readOnly) { | |
857 | dbconn->readOnly = readOnly; | |
858 | } | |
859 | ||
860 | /* Read only connections go to the end of the queue, writeable connections | |
861 | go to the start of the queue. */ | |
862 | SecDbConnectionRef SecDbConnectionAquire(SecDbRef db, bool readOnly, CFErrorRef *error) { | |
863 | CFRetain(db); | |
d8f41ccd | 864 | secdebug("dbconn", "acquire %s connection", readOnly ? "ro" : "rw"); |
427c49bc A |
865 | dispatch_semaphore_wait(readOnly ? db->read_semaphore : db->write_semaphore, DISPATCH_TIME_FOREVER); |
866 | __block SecDbConnectionRef dbconn = NULL; | |
867 | __block bool ok = true; | |
868 | dispatch_sync(db->queue, ^{ | |
869 | if (!db->didFirstOpen) { | |
870 | bool didCreate = false; | |
871 | ok = dbconn = SecDbConnectionCreate(db, false, error); | |
872 | CFErrorRef localError = NULL; | |
873 | if (ok && !SecDbOpenHandle(dbconn, &didCreate, &localError)) { | |
874 | secerror("Unable to create database: %@", localError); | |
875 | if (localError && CFEqual(CFErrorGetDomain(localError), kSecDbErrorDomain)) { | |
876 | int code = (int)CFErrorGetCode(localError); | |
877 | dbconn->isCorrupted = (SQLITE_CORRUPT == code) || (SQLITE_NOTADB == code) || (SQLITE_IOERR == code) || (SQLITE_CANTOPEN == code); | |
878 | } | |
879 | // If the open failure isn't due to corruption, propagte the error. | |
880 | ok = dbconn->isCorrupted; | |
881 | if (!ok && error && *error == NULL) { | |
882 | *error = localError; | |
883 | localError = NULL; | |
884 | } | |
885 | } | |
886 | CFReleaseNull(localError); | |
887 | ||
888 | if (ok) | |
889 | db->didFirstOpen = ok = SecDbDidCreateFirstConnection(dbconn, didCreate, error); | |
890 | if (!ok) | |
891 | CFReleaseNull(dbconn); | |
892 | } else { | |
893 | /* Try to get one from the cache */ | |
894 | CFIndex count = CFArrayGetCount(db->connections); | |
895 | while (count && !dbconn) { | |
896 | CFIndex ix = readOnly ? count - 1 : 0; | |
897 | dbconn = (SecDbConnectionRef)CFArrayGetValueAtIndex(db->connections, ix); | |
898 | if (dbconn) | |
899 | CFRetain(dbconn); | |
900 | else | |
901 | secerror("got NULL dbconn at index: %" PRIdCFIndex " skipping", ix); | |
902 | CFArrayRemoveValueAtIndex(db->connections, ix); | |
903 | } | |
904 | } | |
905 | }); | |
906 | ||
907 | if (dbconn) { | |
908 | /* Make sure the connection we found has the right access */ | |
909 | if (SecDbConnectionIsReadOnly(dbconn) != readOnly) { | |
910 | SecDbConectionSetReadOnly(dbconn, readOnly); | |
911 | } | |
912 | } else if (ok) { | |
913 | /* Nothing found in cache, create a new connection */ | |
914 | bool created = false; | |
915 | dbconn = SecDbConnectionCreate(db, readOnly, error); | |
916 | if (dbconn && !SecDbOpenHandle(dbconn, &created, error)) { | |
917 | CFReleaseNull(dbconn); | |
918 | } | |
919 | } | |
920 | ||
921 | if (!dbconn) { | |
922 | // If aquire fails we need to signal the semaphore again. | |
923 | dispatch_semaphore_signal(readOnly ? db->read_semaphore : db->write_semaphore); | |
924 | CFRelease(db); | |
925 | } | |
926 | ||
927 | return dbconn; | |
928 | } | |
929 | ||
930 | void SecDbConnectionRelease(SecDbConnectionRef dbconn) { | |
931 | if (!dbconn) { | |
932 | secerror("called with NULL dbconn"); | |
933 | return; | |
934 | } | |
935 | SecDbRef db = dbconn->db; | |
936 | secdebug("dbconn", "release %@", dbconn); | |
937 | dispatch_sync(db->queue, ^{ | |
938 | CFIndex count = CFArrayGetCount(db->connections); | |
939 | // Add back possible writable dbconn to the pool. | |
940 | bool readOnly = SecDbConnectionIsReadOnly(dbconn); | |
941 | CFArrayInsertValueAtIndex(db->connections, readOnly ? count : 0, dbconn); | |
942 | // Remove the last (probably read-only) dbconn from the pool. | |
943 | if (count >= kSecDbMaxIdleHandles) { | |
944 | CFArrayRemoveValueAtIndex(db->connections, count); | |
945 | } | |
946 | // Signal after we have put the connection back in the pool of connections | |
947 | dispatch_semaphore_signal(readOnly ? db->read_semaphore : db->write_semaphore); | |
948 | CFRelease(dbconn); | |
949 | CFRelease(db); | |
950 | }); | |
951 | } | |
952 | ||
953 | bool SecDbPerformRead(SecDbRef db, CFErrorRef *error, void (^perform)(SecDbConnectionRef dbconn)) { | |
954 | SecDbConnectionRef dbconn = SecDbConnectionAquire(db, true, error); | |
955 | bool success = false; | |
956 | if (dbconn) { | |
957 | perform(dbconn); | |
958 | success = true; | |
959 | SecDbConnectionRelease(dbconn); | |
960 | } | |
961 | return success; | |
962 | } | |
963 | ||
964 | bool SecDbPerformWrite(SecDbRef db, CFErrorRef *error, void (^perform)(SecDbConnectionRef dbconn)) { | |
965 | SecDbConnectionRef dbconn = SecDbConnectionAquire(db, false, error); | |
966 | bool success = false; | |
967 | if (dbconn) { | |
968 | perform(dbconn); | |
969 | success = true; | |
970 | SecDbConnectionRelease(dbconn); | |
971 | } | |
972 | return success; | |
973 | } | |
974 | ||
975 | static CFStringRef | |
d87e1158 | 976 | SecDbConnectionCopyFormatDescription(CFTypeRef value, CFDictionaryRef formatOptions) |
427c49bc A |
977 | { |
978 | SecDbConnectionRef dbconn = (SecDbConnectionRef)value; | |
979 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<SecDbConnection %s %s>"), | |
980 | dbconn->readOnly ? "ro" : "rw", dbconn->handle ? "open" : "closed"); | |
981 | } | |
982 | ||
983 | static void | |
984 | SecDbConnectionDestroy(CFTypeRef value) | |
985 | { | |
986 | SecDbConnectionRef dbconn = (SecDbConnectionRef)value; | |
987 | if (dbconn->handle) { | |
988 | sqlite3_close(dbconn->handle); | |
989 | } | |
990 | dbconn->db = NULL; | |
991 | } | |
992 | ||
993 | ||
994 | // MARK: - | |
995 | // MARK: Bind helpers | |
996 | ||
997 | #if 0 | |
998 | bool SecDbBindNull(sqlite3_stmt *stmt, int param, CFErrorRef *error) { | |
999 | bool ok = SecDbErrorWithStmt(sqlite3_bind_null(stmt, param), | |
1000 | stmt, error, CFSTR("bind_null[%d]"), param); | |
1001 | secdebug("bind", "bind_null[%d]: %@", param, error ? *error : NULL); | |
1002 | return ok; | |
1003 | } | |
1004 | #endif | |
1005 | ||
1006 | bool SecDbBindBlob(sqlite3_stmt *stmt, int param, const void *zData, size_t n, void(*xDel)(void*), CFErrorRef *error) { | |
1007 | if (n > INT_MAX) { | |
1008 | return SecDbErrorWithStmt(SQLITE_TOOBIG, stmt, error, | |
1009 | CFSTR("bind_blob[%d]: blob bigger than INT_MAX"), param); | |
1010 | } | |
1011 | bool ok = SecDbErrorWithStmt(sqlite3_bind_blob(stmt, param, zData, (int)n, xDel), | |
1012 | stmt, error, CFSTR("bind_blob[%d]"), param); | |
1013 | secdebug("bind", "bind_blob[%d]: %.*s: %@", param, (int)n, zData, error ? *error : NULL); | |
1014 | return ok; | |
1015 | } | |
1016 | ||
1017 | bool SecDbBindText(sqlite3_stmt *stmt, int param, const char *zData, size_t n, void(*xDel)(void*), CFErrorRef *error) { | |
1018 | if (n > INT_MAX) { | |
1019 | return SecDbErrorWithStmt(SQLITE_TOOBIG, stmt, error, | |
1020 | CFSTR("bind_text[%d]: text bigger than INT_MAX"), param); | |
1021 | } | |
1022 | bool ok = SecDbErrorWithStmt(sqlite3_bind_text(stmt, param, zData, (int)n, xDel), stmt, error, | |
1023 | CFSTR("bind_text[%d]"), param); | |
1024 | secdebug("bind", "bind_text[%d]: \"%s\": %@", param, zData, error ? *error : NULL); | |
1025 | return ok; | |
1026 | } | |
1027 | ||
1028 | bool SecDbBindDouble(sqlite3_stmt *stmt, int param, double value, CFErrorRef *error) { | |
1029 | bool ok = SecDbErrorWithStmt(sqlite3_bind_double(stmt, param, value), stmt, error, | |
1030 | CFSTR("bind_double[%d]"), param); | |
1031 | secdebug("bind", "bind_double[%d]: %f: %@", param, value, error ? *error : NULL); | |
1032 | return ok; | |
1033 | } | |
1034 | ||
1035 | bool SecDbBindInt(sqlite3_stmt *stmt, int param, int value, CFErrorRef *error) { | |
1036 | bool ok = SecDbErrorWithStmt(sqlite3_bind_int(stmt, param, value), stmt, error, | |
1037 | CFSTR("bind_int[%d]"), param); | |
1038 | secdebug("bind", "bind_int[%d]: %d: %@", param, value, error ? *error : NULL); | |
1039 | return ok; | |
1040 | } | |
1041 | ||
1042 | bool SecDbBindInt64(sqlite3_stmt *stmt, int param, sqlite3_int64 value, CFErrorRef *error) { | |
1043 | bool ok = SecDbErrorWithStmt(sqlite3_bind_int64(stmt, param, value), stmt, error, | |
1044 | CFSTR("bind_int64[%d]"), param); | |
1045 | secdebug("bind", "bind_int64[%d]: %lld: %@", param, value, error ? *error : NULL); | |
1046 | return ok; | |
1047 | } | |
1048 | ||
1049 | ||
1050 | /* AUDIT[securityd](done): | |
1051 | value (ok) is a caller provided, non NULL CFTypeRef. | |
1052 | */ | |
1053 | bool SecDbBindObject(sqlite3_stmt *stmt, int param, CFTypeRef value, CFErrorRef *error) { | |
1054 | CFTypeID valueId; | |
1055 | __block bool result = false; | |
1056 | ||
1057 | /* TODO: Can we use SQLITE_STATIC below everwhere we currently use | |
1058 | SQLITE_TRANSIENT since we finalize the statement before the value | |
1059 | goes out of scope? */ | |
1060 | if (!value || (valueId = CFGetTypeID(value)) == CFNullGetTypeID()) { | |
1061 | /* Skip bindings for NULL values. sqlite3 will interpret unbound | |
1062 | params as NULL which is exactly what we want. */ | |
1063 | #if 1 | |
1064 | result = true; | |
1065 | #else | |
1066 | result = SecDbBindNull(stmt, param, error); | |
1067 | #endif | |
1068 | } else if (valueId == CFStringGetTypeID()) { | |
1069 | CFStringPerformWithCStringAndLength(value, ^(const char *cstr, size_t clen) { | |
1070 | result = SecDbBindText(stmt, param, cstr, clen, SQLITE_TRANSIENT, error); | |
1071 | }); | |
1072 | } else if (valueId == CFDataGetTypeID()) { | |
1073 | CFIndex len = CFDataGetLength(value); | |
1074 | if (len) { | |
1075 | result = SecDbBindBlob(stmt, param, CFDataGetBytePtr(value), | |
1076 | len, SQLITE_TRANSIENT, error); | |
1077 | } else { | |
1078 | result = SecDbBindText(stmt, param, "", 0, SQLITE_TRANSIENT, error); | |
1079 | } | |
1080 | } else if (valueId == CFDateGetTypeID()) { | |
1081 | CFAbsoluteTime abs_time = CFDateGetAbsoluteTime(value); | |
1082 | result = SecDbBindDouble(stmt, param, abs_time, error); | |
1083 | } else if (valueId == CFBooleanGetTypeID()) { | |
1084 | int bval = CFBooleanGetValue(value); | |
1085 | result = SecDbBindInt(stmt, param, bval, error); | |
1086 | } else if (valueId == CFNumberGetTypeID()) { | |
1087 | Boolean convertOk; | |
1088 | if (CFNumberIsFloatType(value)) { | |
1089 | double nval; | |
1090 | convertOk = CFNumberGetValue(value, kCFNumberDoubleType, &nval); | |
1091 | result = SecDbBindDouble(stmt, param, nval, error); | |
1092 | } else { | |
1093 | int nval; | |
1094 | convertOk = CFNumberGetValue(value, kCFNumberSInt32Type, &nval); | |
1095 | if (convertOk) { | |
1096 | result = SecDbBindInt(stmt, param, nval, error); | |
1097 | } else { | |
1098 | sqlite_int64 nval64; | |
1099 | convertOk = CFNumberGetValue(value, kCFNumberSInt64Type, &nval64); | |
1100 | if (convertOk) | |
1101 | result = SecDbBindInt64(stmt, param, nval64, error); | |
1102 | } | |
1103 | } | |
1104 | if (!convertOk) { | |
1105 | result = SecDbError(SQLITE_INTERNAL, error, CFSTR("bind CFNumberGetValue failed for %@"), value); | |
1106 | } | |
1107 | } else { | |
1108 | if (error) { | |
1109 | CFStringRef valueDesc = CFCopyTypeIDDescription(valueId); | |
1110 | SecDbError(SQLITE_MISMATCH, error, CFSTR("bind unsupported type %@"), valueDesc); | |
1111 | CFReleaseSafe(valueDesc); | |
1112 | } | |
1113 | } | |
1114 | ||
1115 | return result; | |
1116 | } | |
1117 | ||
1118 | // MARK: - | |
1119 | // MARK: SecDbStatementRef | |
1120 | ||
1121 | bool SecDbReset(sqlite3_stmt *stmt, CFErrorRef *error) { | |
1122 | return SecDbErrorWithStmt(sqlite3_reset(stmt), stmt, error, CFSTR("reset")); | |
1123 | } | |
1124 | ||
1125 | bool SecDbClearBindings(sqlite3_stmt *stmt, CFErrorRef *error) { | |
1126 | return SecDbErrorWithStmt(sqlite3_clear_bindings(stmt), stmt, error, CFSTR("clear bindings")); | |
1127 | } | |
1128 | ||
1129 | bool SecDbFinalize(sqlite3_stmt *stmt, CFErrorRef *error) { | |
d87e1158 | 1130 | sqlite3 *handle = sqlite3_db_handle(stmt); |
427c49bc | 1131 | int s3e = sqlite3_finalize(stmt); |
d87e1158 | 1132 | return s3e == SQLITE_OK ? true : SecDbErrorWithDb(s3e, handle, error, CFSTR("finalize: %p"), stmt); |
427c49bc A |
1133 | } |
1134 | ||
1135 | sqlite3_stmt *SecDbPrepareV2(SecDbConnectionRef dbconn, const char *sql, size_t sqlLen, const char **sqlTail, CFErrorRef *error) { | |
1136 | sqlite3 *db = SecDbHandle(dbconn); | |
1137 | if (sqlLen > INT_MAX) { | |
1138 | SecDbErrorWithDb(SQLITE_TOOBIG, db, error, CFSTR("prepare_v2: sql bigger than INT_MAX")); | |
1139 | return NULL; | |
1140 | } | |
1141 | struct timespec sleeptime = { .tv_sec = 0, .tv_nsec = 10000 }; | |
1142 | for (;;) { | |
1143 | sqlite3_stmt *stmt = NULL; | |
1144 | int s3e = sqlite3_prepare_v2(db, sql, (int)sqlLen, &stmt, sqlTail); | |
1145 | if (s3e == SQLITE_OK) | |
1146 | return stmt; | |
1147 | else if (!SecDbWaitIfNeeded(dbconn, s3e, NULL, CFSTR("preparev2"), &sleeptime, error)) | |
1148 | return NULL; | |
1149 | } | |
1150 | } | |
1151 | ||
1152 | static sqlite3_stmt *SecDbCopyStatementWithTailRange(SecDbConnectionRef dbconn, CFStringRef sql, CFRange *sqlTail, CFErrorRef *error) { | |
1153 | __block sqlite3_stmt *stmt = NULL; | |
1154 | if (sql) CFStringPerformWithCStringAndLength(sql, ^(const char *sqlStr, size_t sqlLen) { | |
1155 | const char *tail = NULL; | |
1156 | stmt = SecDbPrepareV2(dbconn, sqlStr, sqlLen, &tail, error); | |
1157 | if (sqlTail && sqlStr < tail && tail < sqlStr + sqlLen) { | |
1158 | sqlTail->location = tail - sqlStr; | |
1159 | sqlTail->length = sqlLen - sqlTail->location; | |
1160 | } | |
1161 | }); | |
1162 | ||
1163 | return stmt; | |
1164 | } | |
1165 | ||
1166 | sqlite3_stmt *SecDbCopyStmt(SecDbConnectionRef dbconn, CFStringRef sql, CFStringRef *tail, CFErrorRef *error) { | |
1167 | // TODO: Add caching and cache lookup of statements | |
1168 | CFRange sqlTail = {}; | |
1169 | sqlite3_stmt *stmt = SecDbCopyStatementWithTailRange(dbconn, sql, &sqlTail, error); | |
1170 | if (sqlTail.length > 0) { | |
1171 | CFStringRef excess = CFStringCreateWithSubstring(CFGetAllocator(sql), sql, sqlTail); | |
1172 | if (tail) { | |
1173 | *tail = excess; | |
1174 | } else { | |
1175 | SecDbError(SQLITE_INTERNAL, error, | |
1176 | CFSTR("prepare_v2: %@ unused sql: %@"), | |
1177 | sql, excess); | |
1178 | CFReleaseSafe(excess); | |
1179 | SecDbFinalize(stmt, error); | |
1180 | stmt = NULL; | |
1181 | } | |
1182 | } | |
1183 | return stmt; | |
1184 | } | |
1185 | ||
1186 | /* | |
1187 | TODO: Could do a hack here with a custom kCFAllocatorNULL allocator for a second CFRuntimeBase inside a SecDbStatement, | |
1188 | TODO: Better yet make a full blow SecDbStatement instance whenever SecDbCopyStmt is called. Then, when the statement is released, in the Dispose method, we Reset and ClearBindings the sqlite3_stmt * and hand it back to the SecDb with the original CFStringRef for the sql (or hash thereof) as an argument. */ | |
1189 | bool SecDbReleaseCachedStmt(SecDbConnectionRef dbconn, CFStringRef sql, sqlite3_stmt *stmt, CFErrorRef *error) { | |
1190 | if (stmt) { | |
d87e1158 | 1191 | return SecDbFinalize(stmt, error); |
427c49bc A |
1192 | } |
1193 | return true; | |
1194 | } | |
1195 | ||
1196 | bool SecDbPrepare(SecDbConnectionRef dbconn, CFStringRef sql, CFErrorRef *error, void(^exec)(sqlite3_stmt *stmt)) { | |
1197 | assert(sql != NULL); | |
1198 | sqlite3_stmt *stmt = SecDbCopyStmt(dbconn, sql, NULL, error); | |
1199 | if (!stmt) | |
1200 | return false; | |
1201 | ||
1202 | exec(stmt); | |
1203 | return SecDbReleaseCachedStmt(dbconn, sql, stmt, error); | |
1204 | } | |
1205 | ||
1206 | bool SecDbWithSQL(SecDbConnectionRef dbconn, CFStringRef sql, CFErrorRef *error, bool(^perform)(sqlite3_stmt *stmt)) { | |
1207 | bool ok = true; | |
1208 | CFRetain(sql); | |
1209 | while (sql) { | |
1210 | CFStringRef tail = NULL; | |
1211 | if (ok) { | |
1212 | sqlite3_stmt *stmt = SecDbCopyStmt(dbconn, sql, &tail, error); | |
1213 | ok = stmt != NULL; | |
1214 | if (stmt) { | |
1215 | if (perform) { | |
1216 | ok = perform(stmt); | |
1217 | } else { | |
1218 | // TODO: Use a different error scope here. | |
1219 | ok = SecError(-50 /* errSecParam */, error, CFSTR("SecDbWithSQL perform block missing")); | |
1220 | } | |
1221 | ok &= SecDbReleaseCachedStmt(dbconn, sql, stmt, error); | |
1222 | } | |
1223 | } else { | |
1224 | // TODO We already have an error here we really just want the left over sql in it's userData | |
1225 | ok = SecDbError(SQLITE_ERROR, error, CFSTR("Error with unexecuted sql remaining %@"), sql); | |
1226 | } | |
1227 | CFRelease(sql); | |
1228 | sql = tail; | |
1229 | } | |
1230 | return ok; | |
1231 | } | |
1232 | ||
1233 | #if 1 | |
1234 | /* SecDbForEach returns true if all SQLITE_ROW returns of sqlite3_step() return true from the row block. | |
1235 | If the row block returns false and doesn't set an error (to indicate it has reached a limit), | |
1236 | this entire function returns false. In that case no error will be set. */ | |
1237 | bool SecDbForEach(sqlite3_stmt *stmt, CFErrorRef *error, bool(^row)(int row_index)) { | |
1238 | bool result = false; | |
1239 | for (int row_ix = 0;;++row_ix) { | |
1240 | int s3e = sqlite3_step(stmt); | |
1241 | if (s3e == SQLITE_ROW) { | |
1242 | if (row) { | |
1243 | if (!row(row_ix)) { | |
1244 | break; | |
1245 | } | |
1246 | } else { | |
1247 | // If we have no row block then getting SQLITE_ROW is an error | |
1248 | SecDbError(s3e, error, | |
1249 | CFSTR("step[%d]: %s returned SQLITE_ROW with NULL row block"), | |
1250 | row_ix, sqlite3_sql(stmt)); | |
1251 | } | |
1252 | } else { | |
1253 | if (s3e == SQLITE_DONE) { | |
1254 | result = true; | |
1255 | } else { | |
1256 | SecDbErrorWithStmt(s3e, stmt, error, CFSTR("step[%d]"), row_ix); | |
1257 | } | |
1258 | break; | |
1259 | } | |
1260 | } | |
1261 | return result; | |
1262 | } | |
1263 | #else | |
1264 | bool SecDbForEach(sqlite3_stmt *stmt, CFErrorRef *error, bool(^row)(int row_index)) { | |
1265 | int row_ix = 0; | |
1266 | for (;;) { | |
1267 | switch (_SecDbStep(dbconn, stmt, error)) { | |
1268 | case kSecDbErrorStep: | |
1269 | return false; | |
1270 | case kSecDbRowStep: | |
1271 | if (row) { | |
1272 | if (row(row_ix++)) | |
1273 | break; | |
1274 | } else { | |
1275 | SecDbError(SQLITE_ERROR, error, CFSTR("SecDbStep SQLITE_ROW returned without a row handler")); | |
1276 | } | |
1277 | return false; | |
1278 | case kSecDbDoneStep: | |
1279 | return true; | |
1280 | } | |
1281 | } | |
1282 | } | |
1283 | #endif | |
1284 | ||
d8f41ccd A |
1285 | void SecDbRecordChange(SecDbConnectionRef dbconn, CFDataRef deleted, CFDataRef inserted) { |
1286 | if (!dbconn->db->notifyPhase) return; | |
1287 | if (deleted) | |
1288 | SOSDigestVectorAppend(&dbconn->todel, CFDataGetBytePtr(deleted)); | |
1289 | if (inserted) | |
1290 | SOSDigestVectorAppend(&dbconn->toadd, CFDataGetBytePtr(inserted)); | |
1291 | if (!dbconn->inTransaction) { | |
1292 | secerror("db %@ changed outside txn", dbconn); | |
1293 | SecDbNotifyPhase(dbconn, kSecDbTransactionWillCommit); | |
1294 | SecDbNotifyPhase(dbconn, kSecDbTransactionDidCommit); | |
1295 | } | |
1296 | } | |
1297 | ||
1298 | ||
427c49bc | 1299 | CFGiblisFor(SecDbConnection) |