1 /* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "AbstractDatabase.h" 31 32 #if ENABLE(DATABASE) 33 #include "DatabaseAuthorizer.h" 34 #include "DatabaseTracker.h" 35 #include "Logging.h" 36 #include "SQLiteStatement.h" 37 #include "ScriptExecutionContext.h" 38 #include "SecurityOrigin.h" 39 #include <wtf/HashMap.h> 40 #include <wtf/HashSet.h> 41 #include <wtf/PassRefPtr.h> 42 #include <wtf/RefPtr.h> 43 #include <wtf/StdLibExtras.h> 44 #include <wtf/text/CString.h> 45 #include <wtf/text/StringHash.h> 46 47 namespace WebCore { 48 49 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) 50 { 51 SQLiteStatement statement(db, query); 52 int result = statement.prepare(); 53 54 if (result != SQLResultOk) { 55 LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); 56 return false; 57 } 58 59 result = statement.step(); 60 if (result == SQLResultRow) { 61 resultString = statement.getColumnText(0); 62 return true; 63 } 64 if (result == SQLResultDone) { 65 resultString = String(); 66 return true; 67 } 68 69 LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); 70 return false; 71 } 72 73 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) 74 { 75 SQLiteStatement statement(db, query); 76 int result = statement.prepare(); 77 78 if (result != SQLResultOk) { 79 LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); 80 return false; 81 } 82 83 statement.bindText(1, value); 84 85 result = statement.step(); 86 if (result != SQLResultDone) { 87 LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); 88 return false; 89 } 90 91 return true; 92 } 93 94 // FIXME: move all guid-related functions to a DatabaseVersionTracker class. 95 static Mutex& guidMutex() 96 { 97 // Note: We don't have to use AtomicallyInitializedStatic here because 98 // this function is called once in the constructor on the main thread 99 // before any other threads that call this function are used. 100 DEFINE_STATIC_LOCAL(Mutex, mutex, ()); 101 return mutex; 102 } 103 104 typedef HashMap<int, String> GuidVersionMap; 105 static GuidVersionMap& guidToVersionMap() 106 { 107 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ()); 108 return map; 109 } 110 111 // NOTE: Caller must lock guidMutex(). 112 static inline void updateGuidVersionMap(int guid, String newVersion) 113 { 114 // Ensure the the mutex is locked. 115 ASSERT(!guidMutex().tryLock()); 116 117 // Note: It is not safe to put an empty string into the guidToVersionMap() map. 118 // That's because the map is cross-thread, but empty strings are per-thread. 119 // The copy() function makes a version of the string you can use on the current 120 // thread, but we need a string we can keep in a cross-thread data structure. 121 // FIXME: This is a quite-awkward restriction to have to program with. 122 123 // Map null string to empty string (see comment above). 124 guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy()); 125 } 126 127 typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap; 128 static GuidDatabaseMap& guidToDatabaseMap() 129 { 130 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ()); 131 return map; 132 } 133 134 static int guidForOriginAndName(const String& origin, const String& name) 135 { 136 String stringID = origin + "/" + name; 137 138 // Note: We don't have to use AtomicallyInitializedStatic here because 139 // this function is called once in the constructor on the main thread 140 // before any other threads that call this function are used. 141 DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ()); 142 MutexLocker locker(stringIdentifierMutex); 143 typedef HashMap<String, int> IDGuidMap; 144 DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ()); 145 int guid = stringIdentifierToGUIDMap.get(stringID); 146 if (!guid) { 147 static int currentNewGUID = 1; 148 guid = currentNewGUID++; 149 stringIdentifierToGUIDMap.set(stringID, guid); 150 } 151 152 return guid; 153 } 154 155 static bool isDatabaseAvailable = true; 156 157 bool AbstractDatabase::isAvailable() 158 { 159 return isDatabaseAvailable; 160 } 161 162 void AbstractDatabase::setIsAvailable(bool available) 163 { 164 isDatabaseAvailable = available; 165 } 166 167 // static 168 const String& AbstractDatabase::databaseInfoTableName() 169 { 170 DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); 171 return name; 172 } 173 174 AbstractDatabase::AbstractDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion, 175 const String& displayName, unsigned long estimatedSize) 176 : m_scriptExecutionContext(context) 177 , m_name(name.crossThreadString()) 178 , m_expectedVersion(expectedVersion.crossThreadString()) 179 , m_displayName(displayName.crossThreadString()) 180 , m_estimatedSize(estimatedSize) 181 , m_guid(0) 182 , m_opened(false) 183 , m_new(false) 184 { 185 ASSERT(context->isContextThread()); 186 m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin(); 187 188 m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName()); 189 190 if (m_name.isNull()) 191 m_name = ""; 192 193 m_guid = guidForOriginAndName(securityOrigin()->toString(), name); 194 { 195 MutexLocker locker(guidMutex()); 196 197 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); 198 if (!hashSet) { 199 hashSet = new HashSet<AbstractDatabase*>; 200 guidToDatabaseMap().set(m_guid, hashSet); 201 } 202 203 hashSet->add(this); 204 } 205 206 m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name); 207 DatabaseTracker::tracker().addOpenDatabase(this); 208 } 209 210 AbstractDatabase::~AbstractDatabase() 211 { 212 } 213 214 void AbstractDatabase::closeDatabase() 215 { 216 if (!m_opened) 217 return; 218 219 m_sqliteDatabase.close(); 220 m_opened = false; 221 { 222 MutexLocker locker(guidMutex()); 223 224 HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); 225 ASSERT(hashSet); 226 ASSERT(hashSet->contains(this)); 227 hashSet->remove(this); 228 if (hashSet->isEmpty()) { 229 guidToDatabaseMap().remove(m_guid); 230 delete hashSet; 231 guidToVersionMap().remove(m_guid); 232 } 233 } 234 } 235 236 String AbstractDatabase::version() const 237 { 238 MutexLocker locker(guidMutex()); 239 return guidToVersionMap().get(m_guid).threadsafeCopy(); 240 } 241 242 static const int maxSqliteBusyWaitTime = 30000; 243 bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec) 244 { 245 if (!m_sqliteDatabase.open(m_filename, true)) { 246 LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); 247 ec = INVALID_STATE_ERR; 248 return false; 249 } 250 if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) 251 LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data()); 252 253 ASSERT(m_databaseAuthorizer); 254 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); 255 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); 256 257 String currentVersion; 258 { 259 MutexLocker locker(guidMutex()); 260 261 GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); 262 if (entry != guidToVersionMap().end()) { 263 // Map null string to empty string (see updateGuidVersionMap()). 264 currentVersion = entry->second.isNull() ? String("") : entry->second; 265 LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); 266 } else { 267 LOG(StorageAPI, "No cached version for guid %i", m_guid); 268 269 if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { 270 m_new = true; 271 272 if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { 273 LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); 274 ec = INVALID_STATE_ERR; 275 // Close the handle to the database file. 276 m_sqliteDatabase.close(); 277 return false; 278 } 279 } 280 281 if (!getVersionFromDatabase(currentVersion)) { 282 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); 283 ec = INVALID_STATE_ERR; 284 // Close the handle to the database file. 285 m_sqliteDatabase.close(); 286 return false; 287 } 288 if (currentVersion.length()) { 289 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); 290 } else if (!m_new || shouldSetVersionInNewDatabase) { 291 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 292 if (!setVersionInDatabase(m_expectedVersion)) { 293 LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 294 ec = INVALID_STATE_ERR; 295 // Close the handle to the database file. 296 m_sqliteDatabase.close(); 297 return false; 298 } 299 currentVersion = m_expectedVersion; 300 } 301 302 updateGuidVersionMap(m_guid, currentVersion); 303 } 304 } 305 306 if (currentVersion.isNull()) { 307 LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); 308 currentVersion = ""; 309 } 310 311 // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. 312 // If the expected version is the empty string, then we always return with whatever version of the database we have. 313 if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) { 314 LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), 315 databaseDebugName().ascii().data(), currentVersion.ascii().data()); 316 ec = INVALID_STATE_ERR; 317 // Close the handle to the database file. 318 m_sqliteDatabase.close(); 319 return false; 320 } 321 322 m_opened = true; 323 324 return true; 325 } 326 327 ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const 328 { 329 return m_scriptExecutionContext.get(); 330 } 331 332 SecurityOrigin* AbstractDatabase::securityOrigin() const 333 { 334 return m_contextThreadSecurityOrigin.get(); 335 } 336 337 String AbstractDatabase::stringIdentifier() const 338 { 339 // Return a deep copy for ref counting thread safety 340 return m_name.threadsafeCopy(); 341 } 342 343 String AbstractDatabase::displayName() const 344 { 345 // Return a deep copy for ref counting thread safety 346 return m_displayName.threadsafeCopy(); 347 } 348 349 unsigned long AbstractDatabase::estimatedSize() const 350 { 351 return m_estimatedSize; 352 } 353 354 String AbstractDatabase::fileName() const 355 { 356 // Return a deep copy for ref counting thread safety 357 return m_filename.threadsafeCopy(); 358 } 359 360 // static 361 const String& AbstractDatabase::databaseVersionKey() 362 { 363 DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey")); 364 return key; 365 } 366 367 bool AbstractDatabase::getVersionFromDatabase(String& version) 368 { 369 DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); 370 371 m_databaseAuthorizer->disable(); 372 373 bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); 374 if (!result) 375 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); 376 377 m_databaseAuthorizer->enable(); 378 379 return result; 380 } 381 382 bool AbstractDatabase::setVersionInDatabase(const String& version) 383 { 384 // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE 385 // clause in the CREATE statement (see Database::performOpenAndVerify()). 386 DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);")); 387 388 m_databaseAuthorizer->disable(); 389 390 bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); 391 if (!result) 392 LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); 393 394 m_databaseAuthorizer->enable(); 395 396 return result; 397 } 398 399 bool AbstractDatabase::versionMatchesExpected() const 400 { 401 if (!m_expectedVersion.isEmpty()) { 402 MutexLocker locker(guidMutex()); 403 return m_expectedVersion == guidToVersionMap().get(m_guid); 404 } 405 406 return true; 407 } 408 409 void AbstractDatabase::setExpectedVersion(const String& version) 410 { 411 m_expectedVersion = version.threadsafeCopy(); 412 // Update the in memory database version map. 413 MutexLocker locker(guidMutex()); 414 updateGuidVersionMap(m_guid, version); 415 } 416 417 void AbstractDatabase::disableAuthorizer() 418 { 419 ASSERT(m_databaseAuthorizer); 420 m_databaseAuthorizer->disable(); 421 } 422 423 void AbstractDatabase::enableAuthorizer() 424 { 425 ASSERT(m_databaseAuthorizer); 426 m_databaseAuthorizer->enable(); 427 } 428 429 void AbstractDatabase::setAuthorizerReadOnly() 430 { 431 ASSERT(m_databaseAuthorizer); 432 m_databaseAuthorizer->setReadOnly(); 433 } 434 435 void AbstractDatabase::setAuthorizerPermissions(int permissions) 436 { 437 ASSERT(m_databaseAuthorizer); 438 m_databaseAuthorizer->setPermissions(permissions); 439 } 440 441 bool AbstractDatabase::lastActionChangedDatabase() 442 { 443 ASSERT(m_databaseAuthorizer); 444 return m_databaseAuthorizer->lastActionChangedDatabase(); 445 } 446 447 bool AbstractDatabase::lastActionWasInsert() 448 { 449 ASSERT(m_databaseAuthorizer); 450 return m_databaseAuthorizer->lastActionWasInsert(); 451 } 452 453 void AbstractDatabase::resetDeletes() 454 { 455 ASSERT(m_databaseAuthorizer); 456 m_databaseAuthorizer->resetDeletes(); 457 } 458 459 bool AbstractDatabase::hadDeletes() 460 { 461 ASSERT(m_databaseAuthorizer); 462 return m_databaseAuthorizer->hadDeletes(); 463 } 464 465 void AbstractDatabase::resetAuthorizer() 466 { 467 if (m_databaseAuthorizer) 468 m_databaseAuthorizer->reset(); 469 } 470 471 unsigned long long AbstractDatabase::maximumSize() const 472 { 473 return DatabaseTracker::tracker().getMaxSizeForDatabase(this); 474 } 475 476 void AbstractDatabase::incrementalVacuumIfNeeded() 477 { 478 int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize(); 479 int64_t totalSize = m_sqliteDatabase.totalSize(); 480 if (totalSize <= 10 * freeSpaceSize) 481 m_sqliteDatabase.runIncrementalVacuumCommand(); 482 } 483 484 void AbstractDatabase::interrupt() 485 { 486 m_sqliteDatabase.interrupt(); 487 } 488 489 bool AbstractDatabase::isInterrupted() 490 { 491 MutexLocker locker(m_sqliteDatabase.databaseMutex()); 492 return m_sqliteDatabase.isInterrupted(); 493 } 494 495 } // namespace WebCore 496 497 #endif // ENABLE(DATABASE) 498