1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/history/thumbnail_database.h" 6 7 #include <algorithm> 8 #include <string> 9 10 #include "base/bind.h" 11 #include "base/debug/alias.h" 12 #include "base/debug/dump_without_crashing.h" 13 #include "base/file_util.h" 14 #include "base/format_macros.h" 15 #include "base/memory/ref_counted_memory.h" 16 #include "base/metrics/histogram.h" 17 #include "base/rand_util.h" 18 #include "base/strings/string_util.h" 19 #include "base/strings/stringprintf.h" 20 #include "base/time/time.h" 21 #include "chrome/browser/history/url_database.h" 22 #include "chrome/common/chrome_version_info.h" 23 #include "sql/recovery.h" 24 #include "sql/statement.h" 25 #include "sql/transaction.h" 26 #include "third_party/sqlite/sqlite3.h" 27 28 #if defined(OS_MACOSX) 29 #include "base/mac/mac_util.h" 30 #endif 31 32 // Description of database tables: 33 // 34 // icon_mapping 35 // id Unique ID. 36 // page_url Page URL which has one or more associated favicons. 37 // icon_id The ID of favicon that this mapping maps to. 38 // 39 // favicons This table associates a row to each favicon for a 40 // |page_url| in the |icon_mapping| table. This is the 41 // default favicon |page_url|/favicon.ico plus any favicons 42 // associated via <link rel="icon_type" href="url">. 43 // The |id| matches the |icon_id| field in the appropriate 44 // row in the icon_mapping table. 45 // 46 // id Unique ID. 47 // url The URL at which the favicon file is located. 48 // icon_type The type of the favicon specified in the rel attribute of 49 // the link tag. The FAVICON type is used for the default 50 // favicon.ico favicon. 51 // 52 // favicon_bitmaps This table contains the PNG encoded bitmap data of the 53 // favicons. There is a separate row for every size in a 54 // multi resolution bitmap. The bitmap data is associated 55 // to the favicon via the |icon_id| field which matches 56 // the |id| field in the appropriate row in the |favicons| 57 // table. 58 // 59 // id Unique ID. 60 // icon_id The ID of the favicon that the bitmap is associated to. 61 // last_updated The time at which this favicon was inserted into the 62 // table. This is used to determine if it needs to be 63 // redownloaded from the web. 64 // image_data PNG encoded data of the favicon. 65 // width Pixel width of |image_data|. 66 // height Pixel height of |image_data|. 67 68 namespace { 69 70 // For this database, schema migrations are deprecated after two 71 // years. This means that the oldest non-deprecated version should be 72 // two years old or greater (thus the migrations to get there are 73 // older). Databases containing deprecated versions will be cleared 74 // at startup. Since this database is a cache, losing old data is not 75 // fatal (in fact, very old data may be expired immediately at startup 76 // anyhow). 77 78 // Version 7: 911a634d/r209424 by qsr (at) chromium.org on 2013-07-01 79 // Version 6: 610f923b/r152367 by pkotwicz (at) chromium.org on 2012-08-20 80 // Version 5: e2ee8ae9/r105004 by groby (at) chromium.org on 2011-10-12 81 // Version 4: 5f104d76/r77288 by sky (at) chromium.org on 2011-03-08 (deprecated) 82 // Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated) 83 84 // Version number of the database. 85 // NOTE(shess): When changing the version, add a new golden file for 86 // the new version and a test to verify that Init() works with it. 87 const int kCurrentVersionNumber = 7; 88 const int kCompatibleVersionNumber = 7; 89 const int kDeprecatedVersionNumber = 4; // and earlier. 90 91 void FillIconMapping(const sql::Statement& statement, 92 const GURL& page_url, 93 history::IconMapping* icon_mapping) { 94 icon_mapping->mapping_id = statement.ColumnInt64(0); 95 icon_mapping->icon_id = statement.ColumnInt64(1); 96 icon_mapping->icon_type = 97 static_cast<favicon_base::IconType>(statement.ColumnInt(2)); 98 icon_mapping->icon_url = GURL(statement.ColumnString(3)); 99 icon_mapping->page_url = page_url; 100 } 101 102 enum InvalidStructureType { 103 // NOTE(shess): Intentionally skip bucket 0 to account for 104 // conversion from a boolean histogram. 105 STRUCTURE_EVENT_FAVICON = 1, 106 STRUCTURE_EVENT_VERSION4, 107 STRUCTURE_EVENT_VERSION5, 108 109 // Always keep this at the end. 110 STRUCTURE_EVENT_MAX, 111 }; 112 113 void RecordInvalidStructure(InvalidStructureType invalid_type) { 114 UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure", 115 invalid_type, STRUCTURE_EVENT_MAX); 116 } 117 118 // Attempt to pass 2000 bytes of |debug_info| into a crash dump. 119 void DumpWithoutCrashing2000(const std::string& debug_info) { 120 char debug_buf[2000]; 121 base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf)); 122 base::debug::Alias(&debug_buf); 123 124 base::debug::DumpWithoutCrashing(); 125 } 126 127 void ReportCorrupt(sql::Connection* db, size_t startup_kb) { 128 // Buffer for accumulating debugging info about the error. Place 129 // more-relevant information earlier, in case things overflow the 130 // fixed-size buffer. 131 std::string debug_info; 132 133 base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n"); 134 135 // Check files up to 8M to keep things from blocking too long. 136 const size_t kMaxIntegrityCheckSize = 8192; 137 if (startup_kb > kMaxIntegrityCheckSize) { 138 base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb); 139 } else { 140 std::vector<std::string> messages; 141 142 const base::TimeTicks before = base::TimeTicks::Now(); 143 db->FullIntegrityCheck(&messages); 144 base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n", 145 (base::TimeTicks::Now() - before).InMilliseconds(), 146 messages.size()); 147 148 // SQLite returns up to 100 messages by default, trim deeper to 149 // keep close to the 2000-character size limit for dumping. 150 // 151 // TODO(shess): If the first 20 tend to be actionable, test if 152 // passing the count to integrity_check makes it exit earlier. In 153 // that case it may be possible to greatly ease the size 154 // restriction. 155 const size_t kMaxMessages = 20; 156 for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) { 157 base::StringAppendF(&debug_info, "%s\n", messages[i].c_str()); 158 } 159 } 160 161 DumpWithoutCrashing2000(debug_info); 162 } 163 164 void ReportError(sql::Connection* db, int error) { 165 // Buffer for accumulating debugging info about the error. Place 166 // more-relevant information earlier, in case things overflow the 167 // fixed-size buffer. 168 std::string debug_info; 169 170 // The error message from the failed operation. 171 base::StringAppendF(&debug_info, "db error: %d/%s\n", 172 db->GetErrorCode(), db->GetErrorMessage()); 173 174 // System errno information. 175 base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno()); 176 177 // SQLITE_ERROR reports seem to be attempts to upgrade invalid 178 // schema, try to log that info. 179 if (error == SQLITE_ERROR) { 180 const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'"; 181 if (db->IsSQLValid(kVersionSql)) { 182 sql::Statement statement(db->GetUniqueStatement(kVersionSql)); 183 if (statement.Step()) { 184 debug_info += "version: "; 185 debug_info += statement.ColumnString(0); 186 debug_info += '\n'; 187 } else if (statement.Succeeded()) { 188 debug_info += "version: none\n"; 189 } else { 190 debug_info += "version: error\n"; 191 } 192 } else { 193 debug_info += "version: invalid\n"; 194 } 195 196 debug_info += "schema:\n"; 197 198 // sqlite_master has columns: 199 // type - "index" or "table". 200 // name - name of created element. 201 // tbl_name - name of element, or target table in case of index. 202 // rootpage - root page of the element in database file. 203 // sql - SQL to create the element. 204 // In general, the |sql| column is sufficient to derive the other 205 // columns. |rootpage| is not interesting for debugging, without 206 // the contents of the database. The COALESCE is because certain 207 // automatic elements will have a |name| but no |sql|, 208 const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master"; 209 sql::Statement statement(db->GetUniqueStatement(kSchemaSql)); 210 while (statement.Step()) { 211 debug_info += statement.ColumnString(0); 212 debug_info += '\n'; 213 } 214 if (!statement.Succeeded()) 215 debug_info += "error\n"; 216 } 217 218 // TODO(shess): Think of other things to log. Not logging the 219 // statement text because the backtrace should suffice in most 220 // cases. The database schema is a possibility, but the 221 // likelihood of recursive error callbacks makes that risky (same 222 // reasoning applies to other data fetched from the database). 223 224 DumpWithoutCrashing2000(debug_info); 225 } 226 227 // TODO(shess): If this proves out, perhaps lift the code out to 228 // chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}. 229 void GenerateDiagnostics(sql::Connection* db, 230 size_t startup_kb, 231 int extended_error) { 232 int error = (extended_error & 0xFF); 233 234 // Infrequently report information about the error up to the crash 235 // server. 236 static const uint64 kReportsPerMillion = 50000; 237 238 // Since some/most errors will not resolve themselves, only report 239 // once per Chrome run. 240 static bool reported = false; 241 if (reported) 242 return; 243 244 uint64 rand = base::RandGenerator(1000000); 245 if (error == SQLITE_CORRUPT) { 246 // Once the database is known to be corrupt, it will generate a 247 // stream of errors until someone fixes it, so give one chance. 248 // Set first in case of errors in generating the report. 249 reported = true; 250 251 // Corrupt cases currently dominate, report them very infrequently. 252 static const uint64 kCorruptReportsPerMillion = 10000; 253 if (rand < kCorruptReportsPerMillion) 254 ReportCorrupt(db, startup_kb); 255 } else if (error == SQLITE_READONLY) { 256 // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it 257 // is seen, it is almost guaranteed to be seen again. 258 reported = true; 259 260 if (rand < kReportsPerMillion) 261 ReportError(db, extended_error); 262 } else { 263 // Only set the flag when making a report. This should allow 264 // later (potentially different) errors in a stream of errors to 265 // be reported. 266 // 267 // TODO(shess): Would it be worthwile to audit for which cases 268 // want once-only handling? Sqlite.Error.Thumbnail shows 269 // CORRUPT and READONLY as almost 95% of all reports on these 270 // channels, so probably easier to just harvest from the field. 271 if (rand < kReportsPerMillion) { 272 reported = true; 273 ReportError(db, extended_error); 274 } 275 } 276 } 277 278 // NOTE(shess): Schema modifications must consider initial creation in 279 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in 280 // |RetainDataForPageUrls()|. 281 bool InitTables(sql::Connection* db) { 282 const char kIconMappingSql[] = 283 "CREATE TABLE IF NOT EXISTS icon_mapping" 284 "(" 285 "id INTEGER PRIMARY KEY," 286 "page_url LONGVARCHAR NOT NULL," 287 "icon_id INTEGER" 288 ")"; 289 if (!db->Execute(kIconMappingSql)) 290 return false; 291 292 const char kFaviconsSql[] = 293 "CREATE TABLE IF NOT EXISTS favicons" 294 "(" 295 "id INTEGER PRIMARY KEY," 296 "url LONGVARCHAR NOT NULL," 297 // default icon_type FAVICON to be consistent with past migration. 298 "icon_type INTEGER DEFAULT 1" 299 ")"; 300 if (!db->Execute(kFaviconsSql)) 301 return false; 302 303 const char kFaviconBitmapsSql[] = 304 "CREATE TABLE IF NOT EXISTS favicon_bitmaps" 305 "(" 306 "id INTEGER PRIMARY KEY," 307 "icon_id INTEGER NOT NULL," 308 "last_updated INTEGER DEFAULT 0," 309 "image_data BLOB," 310 "width INTEGER DEFAULT 0," 311 "height INTEGER DEFAULT 0" 312 ")"; 313 if (!db->Execute(kFaviconBitmapsSql)) 314 return false; 315 316 return true; 317 } 318 319 // NOTE(shess): Schema modifications must consider initial creation in 320 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in 321 // |RetainDataForPageUrls()|. 322 bool InitIndices(sql::Connection* db) { 323 const char kIconMappingUrlIndexSql[] = 324 "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" 325 " ON icon_mapping(page_url)"; 326 const char kIconMappingIdIndexSql[] = 327 "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" 328 " ON icon_mapping(icon_id)"; 329 if (!db->Execute(kIconMappingUrlIndexSql) || 330 !db->Execute(kIconMappingIdIndexSql)) { 331 return false; 332 } 333 334 const char kFaviconsIndexSql[] = 335 "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; 336 if (!db->Execute(kFaviconsIndexSql)) 337 return false; 338 339 const char kFaviconBitmapsIndexSql[] = 340 "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " 341 "favicon_bitmaps(icon_id)"; 342 if (!db->Execute(kFaviconBitmapsIndexSql)) 343 return false; 344 345 return true; 346 } 347 348 enum RecoveryEventType { 349 RECOVERY_EVENT_RECOVERED = 0, 350 RECOVERY_EVENT_FAILED_SCOPER, 351 RECOVERY_EVENT_FAILED_META_VERSION_ERROR, // obsolete 352 RECOVERY_EVENT_FAILED_META_VERSION_NONE, // obsolete 353 RECOVERY_EVENT_FAILED_META_WRONG_VERSION6, // obsolete 354 RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete 355 RECOVERY_EVENT_FAILED_META_WRONG_VERSION, 356 RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete 357 RECOVERY_EVENT_FAILED_META_INSERT, // obsolete 358 RECOVERY_EVENT_FAILED_INIT, 359 RECOVERY_EVENT_FAILED_RECOVER_FAVICONS, // obsolete 360 RECOVERY_EVENT_FAILED_FAVICONS_INSERT, // obsolete 361 RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS, // obsolete 362 RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT, // obsolete 363 RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING, // obsolete 364 RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT, // obsolete 365 RECOVERY_EVENT_RECOVERED_VERSION6, // obsolete 366 RECOVERY_EVENT_FAILED_META_INIT, 367 RECOVERY_EVENT_FAILED_META_VERSION, 368 RECOVERY_EVENT_DEPRECATED, 369 RECOVERY_EVENT_FAILED_V5_INITSCHEMA, // obsolete 370 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, // obsolete 371 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, // obsolete 372 RECOVERY_EVENT_RECOVERED_VERSION5, // obsolete 373 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS, 374 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS, 375 RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING, 376 RECOVERY_EVENT_FAILED_COMMIT, 377 378 // Always keep this at the end. 379 RECOVERY_EVENT_MAX, 380 }; 381 382 void RecordRecoveryEvent(RecoveryEventType recovery_event) { 383 UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery", 384 recovery_event, RECOVERY_EVENT_MAX); 385 } 386 387 // Recover the database to the extent possible, razing it if recovery 388 // is not possible. 389 // TODO(shess): This is mostly just a safe proof of concept. In the 390 // real world, this database is probably not worthwhile recovering, as 391 // opposed to just razing it and starting over whenever corruption is 392 // detected. So this database is a good test subject. 393 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { 394 // NOTE(shess): This code is currently specific to the version 395 // number. I am working on simplifying things to loosen the 396 // dependency, meanwhile contact me if you need to bump the version. 397 DCHECK_EQ(7, kCurrentVersionNumber); 398 399 // TODO(shess): Reset back after? 400 db->reset_error_callback(); 401 402 // For histogram purposes. 403 size_t favicons_rows_recovered = 0; 404 size_t favicon_bitmaps_rows_recovered = 0; 405 size_t icon_mapping_rows_recovered = 0; 406 int64 original_size = 0; 407 base::GetFileSize(db_path, &original_size); 408 409 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); 410 if (!recovery) { 411 // TODO(shess): Unable to create recovery connection. This 412 // implies something substantial is wrong. At this point |db| has 413 // been poisoned so there is nothing really to do. 414 // 415 // Possible responses are unclear. If the failure relates to a 416 // problem somehow specific to the temporary file used to back the 417 // database, then an in-memory database could possibly be used. 418 // This could potentially allow recovering the main database, and 419 // might be simple to implement w/in Begin(). 420 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER); 421 return; 422 } 423 424 // Setup the meta recovery table and fetch the version number from 425 // the corrupt database. 426 int version = 0; 427 if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { 428 // TODO(shess): Prior histograms indicate all failures are in 429 // creating the recover virtual table for corrupt.meta. The table 430 // may not exist, or the database may be too far gone. Either 431 // way, unclear how to resolve. 432 sql::Recovery::Rollback(recovery.Pass()); 433 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION); 434 return; 435 } 436 437 // This code may be able to fetch version information that the regular 438 // deprecation path cannot. 439 // NOTE(shess): v5 and v6 are currently not deprecated in the normal Init() 440 // path, but are deprecated in the recovery path in the interest of keeping 441 // the code simple. http://crbug.com/327485 for numbers. 442 DCHECK_LE(kDeprecatedVersionNumber, 6); 443 if (version <= 6) { 444 sql::Recovery::Unrecoverable(recovery.Pass()); 445 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); 446 return; 447 } 448 449 // Earlier versions have been handled or deprecated, later versions should be 450 // impossible. 451 if (version != 7) { 452 sql::Recovery::Unrecoverable(recovery.Pass()); 453 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); 454 return; 455 } 456 457 // Recover to current schema version. 458 sql::MetaTable recover_meta_table; 459 if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber, 460 kCompatibleVersionNumber)) { 461 sql::Recovery::Rollback(recovery.Pass()); 462 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); 463 return; 464 } 465 466 // Create a fresh version of the database. The recovery code uses 467 // conflict-resolution to handle duplicates, so the indices are 468 // necessary. 469 if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) { 470 // TODO(shess): Unable to create the new schema in the new 471 // database. The new database should be a temporary file, so 472 // being unable to work with it is pretty unclear. 473 // 474 // What are the potential responses, even? The recovery database 475 // could be opened as in-memory. If the temp database had a 476 // filesystem problem and the temp filesystem differs from the 477 // main database, then that could fix it. 478 sql::Recovery::Rollback(recovery.Pass()); 479 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT); 480 return; 481 } 482 483 if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) { 484 sql::Recovery::Rollback(recovery.Pass()); 485 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS); 486 return; 487 } 488 if (!recovery->AutoRecoverTable("favicon_bitmaps", 0, 489 &favicon_bitmaps_rows_recovered)) { 490 sql::Recovery::Rollback(recovery.Pass()); 491 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS); 492 return; 493 } 494 if (!recovery->AutoRecoverTable("icon_mapping", 0, 495 &icon_mapping_rows_recovered)) { 496 sql::Recovery::Rollback(recovery.Pass()); 497 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING); 498 return; 499 } 500 501 // TODO(shess): Is it possible/likely to have broken foreign-key 502 // issues with the tables? 503 // - icon_mapping.icon_id maps to no favicons.id 504 // - favicon_bitmaps.icon_id maps to no favicons.id 505 // - favicons.id is referenced by no icon_mapping.icon_id 506 // - favicons.id is referenced by no favicon_bitmaps.icon_id 507 // This step is possibly not worth the effort necessary to develop 508 // and sequence the statements, as it is basically a form of garbage 509 // collection. 510 511 if (!sql::Recovery::Recovered(recovery.Pass())) { 512 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT); 513 return; 514 } 515 516 // Track the size of the recovered database relative to the size of 517 // the input database. The size should almost always be smaller, 518 // unless the input database was empty to start with. If the 519 // percentage results are very low, something is awry. 520 int64 final_size = 0; 521 if (original_size > 0 && 522 base::GetFileSize(db_path, &final_size) && 523 final_size > 0) { 524 int percentage = static_cast<int>(original_size * 100 / final_size); 525 UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", 526 std::max(100, percentage)); 527 } 528 529 // Using 10,000 because these cases mostly care about "none 530 // recovered" and "lots recovered". More than 10,000 rows recovered 531 // probably means there's something wrong with the profile. 532 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", 533 favicons_rows_recovered); 534 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", 535 favicon_bitmaps_rows_recovered); 536 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", 537 icon_mapping_rows_recovered); 538 539 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); 540 } 541 542 void DatabaseErrorCallback(sql::Connection* db, 543 const base::FilePath& db_path, 544 size_t startup_kb, 545 int extended_error, 546 sql::Statement* stmt) { 547 // TODO(shess): Assert that this is running on a safe thread. 548 // AFAICT, should be the history thread, but at this level I can't 549 // see how to reach that. 550 551 // TODO(shess): For now, don't report on beta or stable so as not to 552 // overwhelm the crash server. Once the big fish are fried, 553 // consider reporting at a reduced rate on the bigger channels. 554 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 555 if (channel != chrome::VersionInfo::CHANNEL_STABLE && 556 channel != chrome::VersionInfo::CHANNEL_BETA) { 557 GenerateDiagnostics(db, startup_kb, extended_error); 558 } 559 560 // Attempt to recover corrupt databases. 561 int error = (extended_error & 0xFF); 562 if (error == SQLITE_CORRUPT || 563 error == SQLITE_CANTOPEN || 564 error == SQLITE_NOTADB) { 565 RecoverDatabaseOrRaze(db, db_path); 566 } 567 568 // The default handling is to assert on debug and to ignore on release. 569 if (!sql::Connection::ShouldIgnoreSqliteError(extended_error)) 570 DLOG(FATAL) << db->GetErrorMessage(); 571 } 572 573 } // namespace 574 575 namespace history { 576 577 ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() { 578 } 579 580 ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() { 581 } 582 583 bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping( 584 IconMapping* icon_mapping) { 585 if (!statement_.Step()) 586 return false; 587 FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping); 588 return true; 589 } 590 591 ThumbnailDatabase::ThumbnailDatabase() { 592 } 593 594 ThumbnailDatabase::~ThumbnailDatabase() { 595 // The DBCloseScoper will delete the DB and the cache. 596 } 597 598 sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) { 599 // TODO(shess): Consider separating database open from schema setup. 600 // With that change, this code could Raze() from outside the 601 // transaction, rather than needing RazeAndClose() in InitImpl(). 602 603 // Retry failed setup in case the recovery system fixed things. 604 const size_t kAttempts = 2; 605 606 sql::InitStatus status = sql::INIT_FAILURE; 607 for (size_t i = 0; i < kAttempts; ++i) { 608 status = InitImpl(db_name); 609 if (status == sql::INIT_OK) 610 return status; 611 612 meta_table_.Reset(); 613 db_.Close(); 614 } 615 return status; 616 } 617 618 void ThumbnailDatabase::ComputeDatabaseMetrics() { 619 sql::Statement favicon_count( 620 db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons")); 621 UMA_HISTOGRAM_COUNTS_10000( 622 "History.NumFaviconsInDB", 623 favicon_count.Step() ? favicon_count.ColumnInt(0) : 0); 624 } 625 626 void ThumbnailDatabase::BeginTransaction() { 627 db_.BeginTransaction(); 628 } 629 630 void ThumbnailDatabase::CommitTransaction() { 631 db_.CommitTransaction(); 632 } 633 634 void ThumbnailDatabase::RollbackTransaction() { 635 db_.RollbackTransaction(); 636 } 637 638 void ThumbnailDatabase::Vacuum() { 639 DCHECK(db_.transaction_nesting() == 0) << 640 "Can not have a transaction when vacuuming."; 641 ignore_result(db_.Execute("VACUUM")); 642 } 643 644 void ThumbnailDatabase::TrimMemory(bool aggressively) { 645 db_.TrimMemory(aggressively); 646 } 647 648 bool ThumbnailDatabase::GetFaviconBitmapIDSizes( 649 favicon_base::FaviconID icon_id, 650 std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { 651 DCHECK(icon_id); 652 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 653 "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); 654 statement.BindInt64(0, icon_id); 655 656 bool result = false; 657 while (statement.Step()) { 658 result = true; 659 if (!bitmap_id_sizes) 660 return result; 661 662 FaviconBitmapIDSize bitmap_id_size; 663 bitmap_id_size.bitmap_id = statement.ColumnInt64(0); 664 bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1), 665 statement.ColumnInt(2)); 666 bitmap_id_sizes->push_back(bitmap_id_size); 667 } 668 return result; 669 } 670 671 bool ThumbnailDatabase::GetFaviconBitmaps( 672 favicon_base::FaviconID icon_id, 673 std::vector<FaviconBitmap>* favicon_bitmaps) { 674 DCHECK(icon_id); 675 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 676 "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps " 677 "WHERE icon_id=?")); 678 statement.BindInt64(0, icon_id); 679 680 bool result = false; 681 while (statement.Step()) { 682 result = true; 683 if (!favicon_bitmaps) 684 return result; 685 686 FaviconBitmap favicon_bitmap; 687 favicon_bitmap.bitmap_id = statement.ColumnInt64(0); 688 favicon_bitmap.icon_id = icon_id; 689 favicon_bitmap.last_updated = 690 base::Time::FromInternalValue(statement.ColumnInt64(1)); 691 if (statement.ColumnByteLength(2) > 0) { 692 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 693 statement.ColumnBlobAsVector(2, &data->data()); 694 favicon_bitmap.bitmap_data = data; 695 } 696 favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3), 697 statement.ColumnInt(4)); 698 favicon_bitmaps->push_back(favicon_bitmap); 699 } 700 return result; 701 } 702 703 bool ThumbnailDatabase::GetFaviconBitmap( 704 FaviconBitmapID bitmap_id, 705 base::Time* last_updated, 706 scoped_refptr<base::RefCountedMemory>* png_icon_data, 707 gfx::Size* pixel_size) { 708 DCHECK(bitmap_id); 709 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 710 "SELECT last_updated, image_data, width, height FROM favicon_bitmaps " 711 "WHERE id=?")); 712 statement.BindInt64(0, bitmap_id); 713 714 if (!statement.Step()) 715 return false; 716 717 if (last_updated) 718 *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0)); 719 720 if (png_icon_data && statement.ColumnByteLength(1) > 0) { 721 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 722 statement.ColumnBlobAsVector(1, &data->data()); 723 *png_icon_data = data; 724 } 725 726 if (pixel_size) { 727 *pixel_size = gfx::Size(statement.ColumnInt(2), 728 statement.ColumnInt(3)); 729 } 730 return true; 731 } 732 733 FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap( 734 favicon_base::FaviconID icon_id, 735 const scoped_refptr<base::RefCountedMemory>& icon_data, 736 base::Time time, 737 const gfx::Size& pixel_size) { 738 DCHECK(icon_id); 739 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 740 "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, " 741 "height) VALUES (?, ?, ?, ?, ?)")); 742 statement.BindInt64(0, icon_id); 743 if (icon_data.get() && icon_data->size()) { 744 statement.BindBlob(1, icon_data->front(), 745 static_cast<int>(icon_data->size())); 746 } else { 747 statement.BindNull(1); 748 } 749 statement.BindInt64(2, time.ToInternalValue()); 750 statement.BindInt(3, pixel_size.width()); 751 statement.BindInt(4, pixel_size.height()); 752 753 if (!statement.Run()) 754 return 0; 755 return db_.GetLastInsertRowId(); 756 } 757 758 bool ThumbnailDatabase::SetFaviconBitmap( 759 FaviconBitmapID bitmap_id, 760 scoped_refptr<base::RefCountedMemory> bitmap_data, 761 base::Time time) { 762 DCHECK(bitmap_id); 763 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 764 "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?")); 765 if (bitmap_data.get() && bitmap_data->size()) { 766 statement.BindBlob(0, bitmap_data->front(), 767 static_cast<int>(bitmap_data->size())); 768 } else { 769 statement.BindNull(0); 770 } 771 statement.BindInt64(1, time.ToInternalValue()); 772 statement.BindInt64(2, bitmap_id); 773 774 return statement.Run(); 775 } 776 777 bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime( 778 FaviconBitmapID bitmap_id, 779 base::Time time) { 780 DCHECK(bitmap_id); 781 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 782 "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?")); 783 statement.BindInt64(0, time.ToInternalValue()); 784 statement.BindInt64(1, bitmap_id); 785 return statement.Run(); 786 } 787 788 bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { 789 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 790 "DELETE FROM favicon_bitmaps WHERE id=?")); 791 statement.BindInt64(0, bitmap_id); 792 return statement.Run(); 793 } 794 795 bool ThumbnailDatabase::SetFaviconOutOfDate(favicon_base::FaviconID icon_id) { 796 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 797 "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); 798 statement.BindInt64(0, 0); 799 statement.BindInt64(1, icon_id); 800 801 return statement.Run(); 802 } 803 804 favicon_base::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( 805 const GURL& icon_url, 806 int required_icon_type, 807 favicon_base::IconType* icon_type) { 808 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 809 "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) " 810 "ORDER BY icon_type DESC")); 811 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 812 statement.BindInt(1, required_icon_type); 813 814 if (!statement.Step()) 815 return 0; // not cached 816 817 if (icon_type) 818 *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1)); 819 return statement.ColumnInt64(0); 820 } 821 822 bool ThumbnailDatabase::GetFaviconHeader(favicon_base::FaviconID icon_id, 823 GURL* icon_url, 824 favicon_base::IconType* icon_type) { 825 DCHECK(icon_id); 826 827 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 828 "SELECT url, icon_type FROM favicons WHERE id=?")); 829 statement.BindInt64(0, icon_id); 830 831 if (!statement.Step()) 832 return false; // No entry for the id. 833 834 if (icon_url) 835 *icon_url = GURL(statement.ColumnString(0)); 836 if (icon_type) 837 *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1)); 838 839 return true; 840 } 841 842 favicon_base::FaviconID ThumbnailDatabase::AddFavicon( 843 const GURL& icon_url, 844 favicon_base::IconType icon_type) { 845 846 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 847 "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); 848 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 849 statement.BindInt(1, icon_type); 850 851 if (!statement.Run()) 852 return 0; 853 return db_.GetLastInsertRowId(); 854 } 855 856 favicon_base::FaviconID ThumbnailDatabase::AddFavicon( 857 const GURL& icon_url, 858 favicon_base::IconType icon_type, 859 const scoped_refptr<base::RefCountedMemory>& icon_data, 860 base::Time time, 861 const gfx::Size& pixel_size) { 862 favicon_base::FaviconID icon_id = AddFavicon(icon_url, icon_type); 863 if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size)) 864 return 0; 865 866 return icon_id; 867 } 868 869 bool ThumbnailDatabase::DeleteFavicon(favicon_base::FaviconID id) { 870 sql::Statement statement; 871 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 872 "DELETE FROM favicons WHERE id = ?")); 873 statement.BindInt64(0, id); 874 if (!statement.Run()) 875 return false; 876 877 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 878 "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); 879 statement.BindInt64(0, id); 880 return statement.Run(); 881 } 882 883 bool ThumbnailDatabase::GetIconMappingsForPageURL( 884 const GURL& page_url, 885 int required_icon_types, 886 std::vector<IconMapping>* filtered_mapping_data) { 887 std::vector<IconMapping> mapping_data; 888 if (!GetIconMappingsForPageURL(page_url, &mapping_data)) 889 return false; 890 891 bool result = false; 892 for (std::vector<IconMapping>::iterator m = mapping_data.begin(); 893 m != mapping_data.end(); ++m) { 894 if (m->icon_type & required_icon_types) { 895 result = true; 896 if (!filtered_mapping_data) 897 return result; 898 899 // Restrict icon type of subsequent matches to |m->icon_type|. 900 // |m->icon_type| is the largest IconType in |mapping_data| because 901 // |mapping_data| is sorted in descending order of IconType. 902 required_icon_types = m->icon_type; 903 904 filtered_mapping_data->push_back(*m); 905 } 906 } 907 return result; 908 } 909 910 bool ThumbnailDatabase::GetIconMappingsForPageURL( 911 const GURL& page_url, 912 std::vector<IconMapping>* mapping_data) { 913 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 914 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 915 "favicons.url " 916 "FROM icon_mapping " 917 "INNER JOIN favicons " 918 "ON icon_mapping.icon_id = favicons.id " 919 "WHERE icon_mapping.page_url=? " 920 "ORDER BY favicons.icon_type DESC")); 921 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 922 923 bool result = false; 924 while (statement.Step()) { 925 result = true; 926 if (!mapping_data) 927 return result; 928 929 IconMapping icon_mapping; 930 FillIconMapping(statement, page_url, &icon_mapping); 931 mapping_data->push_back(icon_mapping); 932 } 933 return result; 934 } 935 936 IconMappingID ThumbnailDatabase::AddIconMapping( 937 const GURL& page_url, 938 favicon_base::FaviconID icon_id) { 939 const char kSql[] = 940 "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)"; 941 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); 942 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 943 statement.BindInt64(1, icon_id); 944 945 if (!statement.Run()) 946 return 0; 947 948 return db_.GetLastInsertRowId(); 949 } 950 951 bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id, 952 favicon_base::FaviconID icon_id) { 953 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 954 "UPDATE icon_mapping SET icon_id=? WHERE id=?")); 955 statement.BindInt64(0, icon_id); 956 statement.BindInt64(1, mapping_id); 957 958 return statement.Run(); 959 } 960 961 bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) { 962 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 963 "DELETE FROM icon_mapping WHERE page_url = ?")); 964 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 965 966 return statement.Run(); 967 } 968 969 bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) { 970 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 971 "DELETE FROM icon_mapping WHERE id=?")); 972 statement.BindInt64(0, mapping_id); 973 974 return statement.Run(); 975 } 976 977 bool ThumbnailDatabase::HasMappingFor(favicon_base::FaviconID id) { 978 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 979 "SELECT id FROM icon_mapping " 980 "WHERE icon_id=?")); 981 statement.BindInt64(0, id); 982 983 return statement.Step(); 984 } 985 986 bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url, 987 const GURL& new_page_url) { 988 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 989 "SELECT icon_id FROM icon_mapping " 990 "WHERE page_url=?")); 991 if (!statement.is_valid()) 992 return false; 993 994 // Do nothing if there are existing bindings 995 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 996 if (statement.Step()) 997 return true; 998 999 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 1000 "INSERT INTO icon_mapping (page_url, icon_id) " 1001 "SELECT ?, icon_id FROM icon_mapping " 1002 "WHERE page_url = ?")); 1003 1004 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 1005 statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url)); 1006 return statement.Run(); 1007 } 1008 1009 bool ThumbnailDatabase::InitIconMappingEnumerator( 1010 favicon_base::IconType type, 1011 IconMappingEnumerator* enumerator) { 1012 DCHECK(!enumerator->statement_.is_valid()); 1013 enumerator->statement_.Assign(db_.GetCachedStatement( 1014 SQL_FROM_HERE, 1015 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 1016 "favicons.url, icon_mapping.page_url " 1017 "FROM icon_mapping JOIN favicons ON (" 1018 "icon_mapping.icon_id = favicons.id) " 1019 "WHERE favicons.icon_type = ?")); 1020 enumerator->statement_.BindInt(0, type); 1021 return enumerator->statement_.is_valid(); 1022 } 1023 1024 bool ThumbnailDatabase::RetainDataForPageUrls( 1025 const std::vector<GURL>& urls_to_keep) { 1026 sql::Transaction transaction(&db_); 1027 if (!transaction.Begin()) 1028 return false; 1029 1030 // temp.icon_id_mapping generates new icon ids as consecutive 1031 // integers starting from 1, and maps them to the old icon ids. 1032 { 1033 const char kIconMappingCreate[] = 1034 "CREATE TEMP TABLE icon_id_mapping " 1035 "(" 1036 "new_icon_id INTEGER PRIMARY KEY," 1037 "old_icon_id INTEGER NOT NULL UNIQUE" 1038 ")"; 1039 if (!db_.Execute(kIconMappingCreate)) 1040 return false; 1041 1042 // Insert the icon ids for retained urls, skipping duplicates. 1043 const char kIconMappingSql[] = 1044 "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " 1045 "SELECT icon_id FROM icon_mapping WHERE page_url = ?"; 1046 sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql)); 1047 for (std::vector<GURL>::const_iterator 1048 i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) { 1049 statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i)); 1050 if (!statement.Run()) 1051 return false; 1052 statement.Reset(true); 1053 } 1054 } 1055 1056 const char kRenameIconMappingTable[] = 1057 "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; 1058 const char kCopyIconMapping[] = 1059 "INSERT INTO icon_mapping (page_url, icon_id) " 1060 "SELECT old.page_url, mapping.new_icon_id " 1061 "FROM old_icon_mapping AS old " 1062 "JOIN temp.icon_id_mapping AS mapping " 1063 "ON (old.icon_id = mapping.old_icon_id)"; 1064 const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; 1065 1066 const char kRenameFaviconsTable[] = 1067 "ALTER TABLE favicons RENAME TO old_favicons"; 1068 const char kCopyFavicons[] = 1069 "INSERT INTO favicons (id, url, icon_type) " 1070 "SELECT mapping.new_icon_id, old.url, old.icon_type " 1071 "FROM old_favicons AS old " 1072 "JOIN temp.icon_id_mapping AS mapping " 1073 "ON (old.id = mapping.old_icon_id)"; 1074 const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; 1075 1076 const char kRenameFaviconBitmapsTable[] = 1077 "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; 1078 const char kCopyFaviconBitmaps[] = 1079 "INSERT INTO favicon_bitmaps " 1080 " (icon_id, last_updated, image_data, width, height) " 1081 "SELECT mapping.new_icon_id, old.last_updated, " 1082 " old.image_data, old.width, old.height " 1083 "FROM old_favicon_bitmaps AS old " 1084 "JOIN temp.icon_id_mapping AS mapping " 1085 "ON (old.icon_id = mapping.old_icon_id)"; 1086 const char kDropOldFaviconBitmapsTable[] = 1087 "DROP TABLE old_favicon_bitmaps"; 1088 1089 // Rename existing tables to new location. 1090 if (!db_.Execute(kRenameIconMappingTable) || 1091 !db_.Execute(kRenameFaviconsTable) || 1092 !db_.Execute(kRenameFaviconBitmapsTable)) { 1093 return false; 1094 } 1095 1096 // Initialize the replacement tables. At this point the old indices 1097 // still exist (pointing to the old_* tables), so do not initialize 1098 // the indices. 1099 if (!InitTables(&db_)) 1100 return false; 1101 1102 // Copy all of the data over. 1103 if (!db_.Execute(kCopyIconMapping) || 1104 !db_.Execute(kCopyFavicons) || 1105 !db_.Execute(kCopyFaviconBitmaps)) { 1106 return false; 1107 } 1108 1109 // Drop the old_* tables, which also drops the indices. 1110 if (!db_.Execute(kDropOldIconMappingTable) || 1111 !db_.Execute(kDropOldFaviconsTable) || 1112 !db_.Execute(kDropOldFaviconBitmapsTable)) { 1113 return false; 1114 } 1115 1116 // Recreate the indices. 1117 // TODO(shess): UNIQUE indices could fail due to duplication. This 1118 // could happen in case of corruption. 1119 if (!InitIndices(&db_)) 1120 return false; 1121 1122 const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; 1123 if (!db_.Execute(kIconMappingDrop)) 1124 return false; 1125 1126 return transaction.Commit(); 1127 } 1128 1129 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, 1130 const base::FilePath& db_name) { 1131 size_t startup_kb = 0; 1132 int64 size_64; 1133 if (base::GetFileSize(db_name, &size_64)) 1134 startup_kb = static_cast<size_t>(size_64 / 1024); 1135 1136 db->set_histogram_tag("Thumbnail"); 1137 db->set_error_callback(base::Bind(&DatabaseErrorCallback, 1138 db, db_name, startup_kb)); 1139 1140 // Thumbnails db now only stores favicons, so we don't need that big a page 1141 // size or cache. 1142 db->set_page_size(2048); 1143 db->set_cache_size(32); 1144 1145 // Run the database in exclusive mode. Nobody else should be accessing the 1146 // database while we're running, and this will give somewhat improved perf. 1147 db->set_exclusive_locking(); 1148 1149 if (!db->Open(db_name)) 1150 return sql::INIT_FAILURE; 1151 1152 return sql::INIT_OK; 1153 } 1154 1155 sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) { 1156 sql::InitStatus status = OpenDatabase(&db_, db_name); 1157 if (status != sql::INIT_OK) 1158 return status; 1159 1160 // Clear databases which are too old to process. 1161 DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber); 1162 sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber); 1163 1164 // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and 1165 // 25. Future versions are not destroyed because that could lead to 1166 // data loss if the profile is opened by a later channel, but 1167 // perhaps a heuristic like >kCurrentVersionNumber+3 could be used. 1168 1169 // Scope initialization in a transaction so we can't be partially initialized. 1170 sql::Transaction transaction(&db_); 1171 if (!transaction.Begin()) 1172 return sql::INIT_FAILURE; 1173 1174 // TODO(shess): Failing Begin() implies that something serious is 1175 // wrong with the database. Raze() may be in order. 1176 1177 #if defined(OS_MACOSX) 1178 // Exclude the thumbnails file from backups. 1179 base::mac::SetFileBackupExclusion(db_name); 1180 #endif 1181 1182 // thumbnails table has been obsolete for a long time, remove any 1183 // detrious. 1184 ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails")); 1185 1186 // At some point, operations involving temporary tables weren't done 1187 // atomically and users have been stranded. Drop those tables and 1188 // move on. 1189 // TODO(shess): Prove it? Audit all cases and see if it's possible 1190 // that this implies non-atomic update, and should thus be handled 1191 // via the corruption handler. 1192 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons")); 1193 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps")); 1194 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping")); 1195 1196 // Create the tables. 1197 if (!meta_table_.Init(&db_, kCurrentVersionNumber, 1198 kCompatibleVersionNumber) || 1199 !InitTables(&db_) || 1200 !InitIndices(&db_)) { 1201 return sql::INIT_FAILURE; 1202 } 1203 1204 // Version check. We should not encounter a database too old for us to handle 1205 // in the wild, so we try to continue in that case. 1206 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { 1207 LOG(WARNING) << "Thumbnail database is too new."; 1208 return sql::INIT_TOO_NEW; 1209 } 1210 1211 int cur_version = meta_table_.GetVersionNumber(); 1212 1213 if (!db_.DoesColumnExist("favicons", "icon_type")) { 1214 LOG(ERROR) << "Raze because of missing favicon.icon_type"; 1215 RecordInvalidStructure(STRUCTURE_EVENT_VERSION4); 1216 1217 db_.RazeAndClose(); 1218 return sql::INIT_FAILURE; 1219 } 1220 1221 if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) { 1222 LOG(ERROR) << "Raze because of missing favicon.sizes"; 1223 RecordInvalidStructure(STRUCTURE_EVENT_VERSION5); 1224 1225 db_.RazeAndClose(); 1226 return sql::INIT_FAILURE; 1227 } 1228 1229 if (cur_version == 5) { 1230 ++cur_version; 1231 if (!UpgradeToVersion6()) 1232 return CantUpgradeToVersion(cur_version); 1233 } 1234 1235 if (cur_version == 6) { 1236 ++cur_version; 1237 if (!UpgradeToVersion7()) 1238 return CantUpgradeToVersion(cur_version); 1239 } 1240 1241 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << 1242 "Thumbnail database version " << cur_version << " is too old to handle."; 1243 1244 // Initialization is complete. 1245 if (!transaction.Commit()) 1246 return sql::INIT_FAILURE; 1247 1248 // Raze the database if the structure of the favicons database is not what 1249 // it should be. This error cannot be detected via the SQL error code because 1250 // the error code for running SQL statements against a database with missing 1251 // columns is SQLITE_ERROR which is not unique enough to act upon. 1252 // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. 1253 // (crbug.com/166453) 1254 if (IsFaviconDBStructureIncorrect()) { 1255 LOG(ERROR) << "Raze because of invalid favicon db structure."; 1256 RecordInvalidStructure(STRUCTURE_EVENT_FAVICON); 1257 1258 db_.RazeAndClose(); 1259 return sql::INIT_FAILURE; 1260 } 1261 1262 return sql::INIT_OK; 1263 } 1264 1265 sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) { 1266 LOG(WARNING) << "Unable to update to thumbnail database to version " << 1267 cur_version << "."; 1268 db_.Close(); 1269 return sql::INIT_FAILURE; 1270 } 1271 1272 bool ThumbnailDatabase::UpgradeToVersion6() { 1273 // Move bitmap data from favicons to favicon_bitmaps. 1274 bool success = 1275 db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, " 1276 "image_data, width, height)" 1277 "SELECT id, last_updated, image_data, 0, 0 FROM favicons") && 1278 db_.Execute("CREATE TABLE temp_favicons (" 1279 "id INTEGER PRIMARY KEY," 1280 "url LONGVARCHAR NOT NULL," 1281 "icon_type INTEGER DEFAULT 1," 1282 // default icon_type FAVICON to be consistent with 1283 // past migration. 1284 "sizes LONGVARCHAR)") && 1285 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1286 "SELECT id, url, icon_type FROM favicons") && 1287 db_.Execute("DROP TABLE favicons") && 1288 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"); 1289 // NOTE(shess): v7 will re-create the index. 1290 if (!success) 1291 return false; 1292 1293 meta_table_.SetVersionNumber(6); 1294 meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber)); 1295 return true; 1296 } 1297 1298 bool ThumbnailDatabase::UpgradeToVersion7() { 1299 // Sizes column was never used, remove it. 1300 bool success = 1301 db_.Execute("CREATE TABLE temp_favicons (" 1302 "id INTEGER PRIMARY KEY," 1303 "url LONGVARCHAR NOT NULL," 1304 // default icon_type FAVICON to be consistent with 1305 // past migration. 1306 "icon_type INTEGER DEFAULT 1)") && 1307 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1308 "SELECT id, url, icon_type FROM favicons") && 1309 db_.Execute("DROP TABLE favicons") && 1310 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") && 1311 db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"); 1312 1313 if (!success) 1314 return false; 1315 1316 meta_table_.SetVersionNumber(7); 1317 meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber)); 1318 return true; 1319 } 1320 1321 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { 1322 return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); 1323 } 1324 1325 } // namespace history 1326