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/file_util.h" 13 #include "base/format_macros.h" 14 #include "base/memory/ref_counted_memory.h" 15 #include "base/metrics/histogram.h" 16 #include "base/rand_util.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/time/time.h" 20 #include "chrome/browser/history/url_database.h" 21 #include "chrome/common/chrome_version_info.h" 22 #include "chrome/common/dump_without_crashing.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<chrome::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 logging::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 // Create v5 schema for recovery code. 279 bool InitSchemaV5(sql::Connection* db) { 280 // This schema was derived from the strings used when v5 was in 281 // force. The [favicons] index and the [icon_mapping] items were 282 // copied from the current strings, after verifying that the 283 // resulting schema exactly matches the schema created by the 284 // original versions of those strings. This allows the linker to 285 // share the strings if they match, while preferring correctness of 286 // the current versions change. 287 288 const char kFaviconsV5[] = 289 "CREATE TABLE IF NOT EXISTS favicons(" 290 "id INTEGER PRIMARY KEY," 291 "url LONGVARCHAR NOT NULL," 292 "last_updated INTEGER DEFAULT 0," 293 "image_data BLOB," 294 "icon_type INTEGER DEFAULT 1," 295 "sizes LONGVARCHAR" 296 ")"; 297 const char kFaviconsIndexV5[] = 298 "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; 299 if (!db->Execute(kFaviconsV5) || !db->Execute(kFaviconsIndexV5)) 300 return false; 301 302 const char kIconMappingV5[] = 303 "CREATE TABLE IF NOT EXISTS icon_mapping" 304 "(" 305 "id INTEGER PRIMARY KEY," 306 "page_url LONGVARCHAR NOT NULL," 307 "icon_id INTEGER" 308 ")"; 309 const char kIconMappingUrlIndexV5[] = 310 "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" 311 " ON icon_mapping(page_url)"; 312 const char kIconMappingIdIndexV5[] = 313 "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" 314 " ON icon_mapping(icon_id)"; 315 if (!db->Execute(kIconMappingV5) || 316 !db->Execute(kIconMappingUrlIndexV5) || 317 !db->Execute(kIconMappingIdIndexV5)) { 318 return false; 319 } 320 321 return true; 322 } 323 324 // TODO(shess): Consider InitSchemaV7(). InitSchemaV5() is worthwhile 325 // because there appear to be 10s of thousands of marooned v5 326 // databases in the wild. Once recovery reaches stable, the number of 327 // corrupt-but-recoverable databases should drop, possibly to the 328 // point where it is not worthwhile to maintain previous-version 329 // recovery code. 330 // TODO(shess): Alternately, think on a way to more cleanly represent 331 // versioned schema going forward. 332 bool InitTables(sql::Connection* db) { 333 const char kIconMappingSql[] = 334 "CREATE TABLE IF NOT EXISTS icon_mapping" 335 "(" 336 "id INTEGER PRIMARY KEY," 337 "page_url LONGVARCHAR NOT NULL," 338 "icon_id INTEGER" 339 ")"; 340 if (!db->Execute(kIconMappingSql)) 341 return false; 342 343 const char kFaviconsSql[] = 344 "CREATE TABLE IF NOT EXISTS favicons" 345 "(" 346 "id INTEGER PRIMARY KEY," 347 "url LONGVARCHAR NOT NULL," 348 // default icon_type FAVICON to be consistent with past migration. 349 "icon_type INTEGER DEFAULT 1" 350 ")"; 351 if (!db->Execute(kFaviconsSql)) 352 return false; 353 354 const char kFaviconBitmapsSql[] = 355 "CREATE TABLE IF NOT EXISTS favicon_bitmaps" 356 "(" 357 "id INTEGER PRIMARY KEY," 358 "icon_id INTEGER NOT NULL," 359 "last_updated INTEGER DEFAULT 0," 360 "image_data BLOB," 361 "width INTEGER DEFAULT 0," 362 "height INTEGER DEFAULT 0" 363 ")"; 364 if (!db->Execute(kFaviconBitmapsSql)) 365 return false; 366 367 return true; 368 } 369 370 bool InitIndices(sql::Connection* db) { 371 const char kIconMappingUrlIndexSql[] = 372 "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" 373 " ON icon_mapping(page_url)"; 374 const char kIconMappingIdIndexSql[] = 375 "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" 376 " ON icon_mapping(icon_id)"; 377 if (!db->Execute(kIconMappingUrlIndexSql) || 378 !db->Execute(kIconMappingIdIndexSql)) { 379 return false; 380 } 381 382 const char kFaviconsIndexSql[] = 383 "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; 384 if (!db->Execute(kFaviconsIndexSql)) 385 return false; 386 387 const char kFaviconBitmapsIndexSql[] = 388 "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " 389 "favicon_bitmaps(icon_id)"; 390 if (!db->Execute(kFaviconBitmapsIndexSql)) 391 return false; 392 393 return true; 394 } 395 396 enum RecoveryEventType { 397 RECOVERY_EVENT_RECOVERED = 0, 398 RECOVERY_EVENT_FAILED_SCOPER, 399 RECOVERY_EVENT_FAILED_META_VERSION_ERROR, // obsolete 400 RECOVERY_EVENT_FAILED_META_VERSION_NONE, // obsolete 401 RECOVERY_EVENT_FAILED_META_WRONG_VERSION6, // obsolete 402 RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete 403 RECOVERY_EVENT_FAILED_META_WRONG_VERSION, 404 RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete 405 RECOVERY_EVENT_FAILED_META_INSERT, // obsolete 406 RECOVERY_EVENT_FAILED_INIT, 407 RECOVERY_EVENT_FAILED_RECOVER_FAVICONS, // obsolete 408 RECOVERY_EVENT_FAILED_FAVICONS_INSERT, // obsolete 409 RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS, // obsolete 410 RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT, // obsolete 411 RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING, // obsolete 412 RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT, // obsolete 413 RECOVERY_EVENT_RECOVERED_VERSION6, 414 RECOVERY_EVENT_FAILED_META_INIT, 415 RECOVERY_EVENT_FAILED_META_VERSION, 416 RECOVERY_EVENT_DEPRECATED, 417 RECOVERY_EVENT_FAILED_V5_INITSCHEMA, 418 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, 419 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, 420 RECOVERY_EVENT_RECOVERED_VERSION5, 421 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS, 422 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS, 423 RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING, 424 RECOVERY_EVENT_FAILED_COMMIT, 425 426 // Always keep this at the end. 427 RECOVERY_EVENT_MAX, 428 }; 429 430 void RecordRecoveryEvent(RecoveryEventType recovery_event) { 431 UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery", 432 recovery_event, RECOVERY_EVENT_MAX); 433 } 434 435 // Recover the database to the extent possible, razing it if recovery 436 // is not possible. 437 // TODO(shess): This is mostly just a safe proof of concept. In the 438 // real world, this database is probably not worthwhile recovering, as 439 // opposed to just razing it and starting over whenever corruption is 440 // detected. So this database is a good test subject. 441 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { 442 // NOTE(shess): This code is currently specific to the version 443 // number. I am working on simplifying things to loosen the 444 // dependency, meanwhile contact me if you need to bump the version. 445 DCHECK_EQ(7, kCurrentVersionNumber); 446 447 // TODO(shess): Reset back after? 448 db->reset_error_callback(); 449 450 // For histogram purposes. 451 size_t favicons_rows_recovered = 0; 452 size_t favicon_bitmaps_rows_recovered = 0; 453 size_t icon_mapping_rows_recovered = 0; 454 int64 original_size = 0; 455 base::GetFileSize(db_path, &original_size); 456 457 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); 458 if (!recovery) { 459 // TODO(shess): Unable to create recovery connection. This 460 // implies something substantial is wrong. At this point |db| has 461 // been poisoned so there is nothing really to do. 462 // 463 // Possible responses are unclear. If the failure relates to a 464 // problem somehow specific to the temporary file used to back the 465 // database, then an in-memory database could possibly be used. 466 // This could potentially allow recovering the main database, and 467 // might be simple to implement w/in Begin(). 468 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER); 469 return; 470 } 471 472 // Setup the meta recovery table and fetch the version number from 473 // the corrupt database. 474 int version = 0; 475 if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { 476 // TODO(shess): Prior histograms indicate all failures are in 477 // creating the recover virtual table for corrupt.meta. The table 478 // may not exist, or the database may be too far gone. Either 479 // way, unclear how to resolve. 480 sql::Recovery::Rollback(recovery.Pass()); 481 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION); 482 return; 483 } 484 485 // Recover v5 database to v5 schema. Next pass through Init() will 486 // migrate to v7. 487 if (version == 5) { 488 sql::MetaTable recover_meta_table; 489 if (!recover_meta_table.Init(recovery->db(), version, version)) { 490 sql::Recovery::Rollback(recovery.Pass()); 491 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); 492 return; 493 } 494 495 // TODO(shess): These tests are separate for histogram purposes, 496 // but once things look stable it can be tightened up. 497 if (!InitSchemaV5(recovery->db())) { 498 sql::Recovery::Rollback(recovery.Pass()); 499 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_INITSCHEMA); 500 return; 501 } 502 503 if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) { 504 sql::Recovery::Rollback(recovery.Pass()); 505 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS); 506 return; 507 } 508 509 if (!recovery->AutoRecoverTable("icon_mapping", 0, 510 &icon_mapping_rows_recovered)) { 511 sql::Recovery::Rollback(recovery.Pass()); 512 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING); 513 return; 514 } 515 516 ignore_result(sql::Recovery::Recovered(recovery.Pass())); 517 518 // TODO(shess): Could this code be shared with the v6/7 code 519 // without requiring too much state to be carried? 520 521 // Track the size of the recovered database relative to the size of 522 // the input database. The size should almost always be smaller, 523 // unless the input database was empty to start with. If the 524 // percentage results are very low, something is awry. 525 int64 final_size = 0; 526 if (original_size > 0 && 527 base::GetFileSize(db_path, &final_size) && 528 final_size > 0) { 529 UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", 530 final_size * 100 / original_size); 531 } 532 533 // Using 10,000 because these cases mostly care about "none 534 // recovered" and "lots recovered". More than 10,000 rows recovered 535 // probably means there's something wrong with the profile. 536 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", 537 favicons_rows_recovered); 538 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", 539 icon_mapping_rows_recovered); 540 541 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION5); 542 return; 543 } 544 545 // This code may be able to fetch versions that the regular 546 // deprecation path cannot. 547 if (version <= kDeprecatedVersionNumber) { 548 sql::Recovery::Unrecoverable(recovery.Pass()); 549 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); 550 return; 551 } 552 553 // TODO(shess): Earlier versions have been handled or deprecated, 554 // later versions should be impossible. Unrecoverable() seems 555 // reasonable. 556 if (version != 6 && version != 7) { 557 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); 558 sql::Recovery::Rollback(recovery.Pass()); 559 return; 560 } 561 562 // Both v6 and v7 recover to current schema version. 563 sql::MetaTable recover_meta_table; 564 if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber, 565 kCompatibleVersionNumber)) { 566 sql::Recovery::Rollback(recovery.Pass()); 567 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); 568 return; 569 } 570 571 // Create a fresh version of the database. The recovery code uses 572 // conflict-resolution to handle duplicates, so the indices are 573 // necessary. 574 if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) { 575 // TODO(shess): Unable to create the new schema in the new 576 // database. The new database should be a temporary file, so 577 // being unable to work with it is pretty unclear. 578 // 579 // What are the potential responses, even? The recovery database 580 // could be opened as in-memory. If the temp database had a 581 // filesystem problem and the temp filesystem differs from the 582 // main database, then that could fix it. 583 sql::Recovery::Rollback(recovery.Pass()); 584 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT); 585 return; 586 } 587 588 // [favicons] differs because v6 had an unused [sizes] column which 589 // was removed in v7. 590 if (!recovery->AutoRecoverTable("favicons", 1, &favicons_rows_recovered)) { 591 sql::Recovery::Rollback(recovery.Pass()); 592 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS); 593 return; 594 } 595 if (!recovery->AutoRecoverTable("favicon_bitmaps", 0, 596 &favicon_bitmaps_rows_recovered)) { 597 sql::Recovery::Rollback(recovery.Pass()); 598 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS); 599 return; 600 } 601 if (!recovery->AutoRecoverTable("icon_mapping", 0, 602 &icon_mapping_rows_recovered)) { 603 sql::Recovery::Rollback(recovery.Pass()); 604 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING); 605 return; 606 } 607 608 // TODO(shess): Is it possible/likely to have broken foreign-key 609 // issues with the tables? 610 // - icon_mapping.icon_id maps to no favicons.id 611 // - favicon_bitmaps.icon_id maps to no favicons.id 612 // - favicons.id is referenced by no icon_mapping.icon_id 613 // - favicons.id is referenced by no favicon_bitmaps.icon_id 614 // This step is possibly not worth the effort necessary to develop 615 // and sequence the statements, as it is basically a form of garbage 616 // collection. 617 618 if (!sql::Recovery::Recovered(recovery.Pass())) { 619 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT); 620 return; 621 } 622 623 // Track the size of the recovered database relative to the size of 624 // the input database. The size should almost always be smaller, 625 // unless the input database was empty to start with. If the 626 // percentage results are very low, something is awry. 627 int64 final_size = 0; 628 if (original_size > 0 && 629 base::GetFileSize(db_path, &final_size) && 630 final_size > 0) { 631 int percentage = static_cast<int>(original_size * 100 / final_size); 632 UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", 633 std::max(100, percentage)); 634 } 635 636 // Using 10,000 because these cases mostly care about "none 637 // recovered" and "lots recovered". More than 10,000 rows recovered 638 // probably means there's something wrong with the profile. 639 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", 640 favicons_rows_recovered); 641 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", 642 favicon_bitmaps_rows_recovered); 643 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", 644 icon_mapping_rows_recovered); 645 646 if (version == 6) { 647 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION6); 648 } else { 649 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); 650 } 651 } 652 653 void DatabaseErrorCallback(sql::Connection* db, 654 const base::FilePath& db_path, 655 size_t startup_kb, 656 int extended_error, 657 sql::Statement* stmt) { 658 // TODO(shess): Assert that this is running on a safe thread. 659 // AFAICT, should be the history thread, but at this level I can't 660 // see how to reach that. 661 662 // TODO(shess): For now, don't report on beta or stable so as not to 663 // overwhelm the crash server. Once the big fish are fried, 664 // consider reporting at a reduced rate on the bigger channels. 665 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 666 if (channel != chrome::VersionInfo::CHANNEL_STABLE && 667 channel != chrome::VersionInfo::CHANNEL_BETA) { 668 GenerateDiagnostics(db, startup_kb, extended_error); 669 } 670 671 // Attempt to recover corrupt databases. 672 int error = (extended_error & 0xFF); 673 if (error == SQLITE_CORRUPT || 674 error == SQLITE_CANTOPEN || 675 error == SQLITE_NOTADB) { 676 RecoverDatabaseOrRaze(db, db_path); 677 } 678 679 // The default handling is to assert on debug and to ignore on release. 680 if (!sql::Connection::ShouldIgnoreSqliteError(extended_error)) 681 DLOG(FATAL) << db->GetErrorMessage(); 682 } 683 684 } // namespace 685 686 namespace history { 687 688 ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() { 689 } 690 691 ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() { 692 } 693 694 bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping( 695 IconMapping* icon_mapping) { 696 if (!statement_.Step()) 697 return false; 698 FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping); 699 return true; 700 } 701 702 ThumbnailDatabase::ThumbnailDatabase() { 703 } 704 705 ThumbnailDatabase::~ThumbnailDatabase() { 706 // The DBCloseScoper will delete the DB and the cache. 707 } 708 709 sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) { 710 // TODO(shess): Consider separating database open from schema setup. 711 // With that change, this code could Raze() from outside the 712 // transaction, rather than needing RazeAndClose() in InitImpl(). 713 714 // Retry failed setup in case the recovery system fixed things. 715 const size_t kAttempts = 2; 716 717 sql::InitStatus status = sql::INIT_FAILURE; 718 for (size_t i = 0; i < kAttempts; ++i) { 719 status = InitImpl(db_name); 720 if (status == sql::INIT_OK) 721 return status; 722 723 meta_table_.Reset(); 724 db_.Close(); 725 } 726 return status; 727 } 728 729 void ThumbnailDatabase::ComputeDatabaseMetrics() { 730 sql::Statement favicon_count( 731 db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons")); 732 UMA_HISTOGRAM_COUNTS_10000( 733 "History.NumFaviconsInDB", 734 favicon_count.Step() ? favicon_count.ColumnInt(0) : 0); 735 } 736 737 void ThumbnailDatabase::BeginTransaction() { 738 db_.BeginTransaction(); 739 } 740 741 void ThumbnailDatabase::CommitTransaction() { 742 db_.CommitTransaction(); 743 } 744 745 void ThumbnailDatabase::RollbackTransaction() { 746 db_.RollbackTransaction(); 747 } 748 749 void ThumbnailDatabase::Vacuum() { 750 DCHECK(db_.transaction_nesting() == 0) << 751 "Can not have a transaction when vacuuming."; 752 ignore_result(db_.Execute("VACUUM")); 753 } 754 755 void ThumbnailDatabase::TrimMemory(bool aggressively) { 756 db_.TrimMemory(aggressively); 757 } 758 759 bool ThumbnailDatabase::GetFaviconBitmapIDSizes( 760 chrome::FaviconID icon_id, 761 std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { 762 DCHECK(icon_id); 763 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 764 "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); 765 statement.BindInt64(0, icon_id); 766 767 bool result = false; 768 while (statement.Step()) { 769 result = true; 770 if (!bitmap_id_sizes) 771 return result; 772 773 FaviconBitmapIDSize bitmap_id_size; 774 bitmap_id_size.bitmap_id = statement.ColumnInt64(0); 775 bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1), 776 statement.ColumnInt(2)); 777 bitmap_id_sizes->push_back(bitmap_id_size); 778 } 779 return result; 780 } 781 782 bool ThumbnailDatabase::GetFaviconBitmaps( 783 chrome::FaviconID icon_id, 784 std::vector<FaviconBitmap>* favicon_bitmaps) { 785 DCHECK(icon_id); 786 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 787 "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps " 788 "WHERE icon_id=?")); 789 statement.BindInt64(0, icon_id); 790 791 bool result = false; 792 while (statement.Step()) { 793 result = true; 794 if (!favicon_bitmaps) 795 return result; 796 797 FaviconBitmap favicon_bitmap; 798 favicon_bitmap.bitmap_id = statement.ColumnInt64(0); 799 favicon_bitmap.icon_id = icon_id; 800 favicon_bitmap.last_updated = 801 base::Time::FromInternalValue(statement.ColumnInt64(1)); 802 if (statement.ColumnByteLength(2) > 0) { 803 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 804 statement.ColumnBlobAsVector(2, &data->data()); 805 favicon_bitmap.bitmap_data = data; 806 } 807 favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3), 808 statement.ColumnInt(4)); 809 favicon_bitmaps->push_back(favicon_bitmap); 810 } 811 return result; 812 } 813 814 bool ThumbnailDatabase::GetFaviconBitmap( 815 FaviconBitmapID bitmap_id, 816 base::Time* last_updated, 817 scoped_refptr<base::RefCountedMemory>* png_icon_data, 818 gfx::Size* pixel_size) { 819 DCHECK(bitmap_id); 820 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 821 "SELECT last_updated, image_data, width, height FROM favicon_bitmaps " 822 "WHERE id=?")); 823 statement.BindInt64(0, bitmap_id); 824 825 if (!statement.Step()) 826 return false; 827 828 if (last_updated) 829 *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0)); 830 831 if (png_icon_data && statement.ColumnByteLength(1) > 0) { 832 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 833 statement.ColumnBlobAsVector(1, &data->data()); 834 *png_icon_data = data; 835 } 836 837 if (pixel_size) { 838 *pixel_size = gfx::Size(statement.ColumnInt(2), 839 statement.ColumnInt(3)); 840 } 841 return true; 842 } 843 844 FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap( 845 chrome::FaviconID icon_id, 846 const scoped_refptr<base::RefCountedMemory>& icon_data, 847 base::Time time, 848 const gfx::Size& pixel_size) { 849 DCHECK(icon_id); 850 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 851 "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, " 852 "height) VALUES (?, ?, ?, ?, ?)")); 853 statement.BindInt64(0, icon_id); 854 if (icon_data.get() && icon_data->size()) { 855 statement.BindBlob(1, icon_data->front(), 856 static_cast<int>(icon_data->size())); 857 } else { 858 statement.BindNull(1); 859 } 860 statement.BindInt64(2, time.ToInternalValue()); 861 statement.BindInt(3, pixel_size.width()); 862 statement.BindInt(4, pixel_size.height()); 863 864 if (!statement.Run()) 865 return 0; 866 return db_.GetLastInsertRowId(); 867 } 868 869 bool ThumbnailDatabase::SetFaviconBitmap( 870 FaviconBitmapID bitmap_id, 871 scoped_refptr<base::RefCountedMemory> bitmap_data, 872 base::Time time) { 873 DCHECK(bitmap_id); 874 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 875 "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?")); 876 if (bitmap_data.get() && bitmap_data->size()) { 877 statement.BindBlob(0, bitmap_data->front(), 878 static_cast<int>(bitmap_data->size())); 879 } else { 880 statement.BindNull(0); 881 } 882 statement.BindInt64(1, time.ToInternalValue()); 883 statement.BindInt64(2, bitmap_id); 884 885 return statement.Run(); 886 } 887 888 bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime( 889 FaviconBitmapID bitmap_id, 890 base::Time time) { 891 DCHECK(bitmap_id); 892 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 893 "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?")); 894 statement.BindInt64(0, time.ToInternalValue()); 895 statement.BindInt64(1, bitmap_id); 896 return statement.Run(); 897 } 898 899 bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { 900 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 901 "DELETE FROM favicon_bitmaps WHERE id=?")); 902 statement.BindInt64(0, bitmap_id); 903 return statement.Run(); 904 } 905 906 bool ThumbnailDatabase::SetFaviconOutOfDate(chrome::FaviconID icon_id) { 907 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 908 "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); 909 statement.BindInt64(0, 0); 910 statement.BindInt64(1, icon_id); 911 912 return statement.Run(); 913 } 914 915 chrome::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( 916 const GURL& icon_url, 917 int required_icon_type, 918 chrome::IconType* icon_type) { 919 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 920 "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) " 921 "ORDER BY icon_type DESC")); 922 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 923 statement.BindInt(1, required_icon_type); 924 925 if (!statement.Step()) 926 return 0; // not cached 927 928 if (icon_type) 929 *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); 930 return statement.ColumnInt64(0); 931 } 932 933 bool ThumbnailDatabase::GetFaviconHeader(chrome::FaviconID icon_id, 934 GURL* icon_url, 935 chrome::IconType* icon_type) { 936 DCHECK(icon_id); 937 938 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 939 "SELECT url, icon_type FROM favicons WHERE id=?")); 940 statement.BindInt64(0, icon_id); 941 942 if (!statement.Step()) 943 return false; // No entry for the id. 944 945 if (icon_url) 946 *icon_url = GURL(statement.ColumnString(0)); 947 if (icon_type) 948 *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); 949 950 return true; 951 } 952 953 chrome::FaviconID ThumbnailDatabase::AddFavicon( 954 const GURL& icon_url, 955 chrome::IconType icon_type) { 956 957 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 958 "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); 959 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 960 statement.BindInt(1, icon_type); 961 962 if (!statement.Run()) 963 return 0; 964 return db_.GetLastInsertRowId(); 965 } 966 967 chrome::FaviconID ThumbnailDatabase::AddFavicon( 968 const GURL& icon_url, 969 chrome::IconType icon_type, 970 const scoped_refptr<base::RefCountedMemory>& icon_data, 971 base::Time time, 972 const gfx::Size& pixel_size) { 973 chrome::FaviconID icon_id = AddFavicon(icon_url, icon_type); 974 if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size)) 975 return 0; 976 977 return icon_id; 978 } 979 980 bool ThumbnailDatabase::DeleteFavicon(chrome::FaviconID id) { 981 sql::Statement statement; 982 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 983 "DELETE FROM favicons WHERE id = ?")); 984 statement.BindInt64(0, id); 985 if (!statement.Run()) 986 return false; 987 988 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 989 "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); 990 statement.BindInt64(0, id); 991 return statement.Run(); 992 } 993 994 bool ThumbnailDatabase::GetIconMappingsForPageURL( 995 const GURL& page_url, 996 int required_icon_types, 997 std::vector<IconMapping>* filtered_mapping_data) { 998 std::vector<IconMapping> mapping_data; 999 if (!GetIconMappingsForPageURL(page_url, &mapping_data)) 1000 return false; 1001 1002 bool result = false; 1003 for (std::vector<IconMapping>::iterator m = mapping_data.begin(); 1004 m != mapping_data.end(); ++m) { 1005 if (m->icon_type & required_icon_types) { 1006 result = true; 1007 if (!filtered_mapping_data) 1008 return result; 1009 1010 // Restrict icon type of subsequent matches to |m->icon_type|. 1011 // |m->icon_type| is the largest IconType in |mapping_data| because 1012 // |mapping_data| is sorted in descending order of IconType. 1013 required_icon_types = m->icon_type; 1014 1015 filtered_mapping_data->push_back(*m); 1016 } 1017 } 1018 return result; 1019 } 1020 1021 bool ThumbnailDatabase::GetIconMappingsForPageURL( 1022 const GURL& page_url, 1023 std::vector<IconMapping>* mapping_data) { 1024 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1025 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 1026 "favicons.url " 1027 "FROM icon_mapping " 1028 "INNER JOIN favicons " 1029 "ON icon_mapping.icon_id = favicons.id " 1030 "WHERE icon_mapping.page_url=? " 1031 "ORDER BY favicons.icon_type DESC")); 1032 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 1033 1034 bool result = false; 1035 while (statement.Step()) { 1036 result = true; 1037 if (!mapping_data) 1038 return result; 1039 1040 IconMapping icon_mapping; 1041 FillIconMapping(statement, page_url, &icon_mapping); 1042 mapping_data->push_back(icon_mapping); 1043 } 1044 return result; 1045 } 1046 1047 IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url, 1048 chrome::FaviconID icon_id) { 1049 const char kSql[] = 1050 "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)"; 1051 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); 1052 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 1053 statement.BindInt64(1, icon_id); 1054 1055 if (!statement.Run()) 1056 return 0; 1057 1058 return db_.GetLastInsertRowId(); 1059 } 1060 1061 bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id, 1062 chrome::FaviconID icon_id) { 1063 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1064 "UPDATE icon_mapping SET icon_id=? WHERE id=?")); 1065 statement.BindInt64(0, icon_id); 1066 statement.BindInt64(1, mapping_id); 1067 1068 return statement.Run(); 1069 } 1070 1071 bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) { 1072 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1073 "DELETE FROM icon_mapping WHERE page_url = ?")); 1074 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 1075 1076 return statement.Run(); 1077 } 1078 1079 bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) { 1080 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1081 "DELETE FROM icon_mapping WHERE id=?")); 1082 statement.BindInt64(0, mapping_id); 1083 1084 return statement.Run(); 1085 } 1086 1087 bool ThumbnailDatabase::HasMappingFor(chrome::FaviconID id) { 1088 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1089 "SELECT id FROM icon_mapping " 1090 "WHERE icon_id=?")); 1091 statement.BindInt64(0, id); 1092 1093 return statement.Step(); 1094 } 1095 1096 bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url, 1097 const GURL& new_page_url) { 1098 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 1099 "SELECT icon_id FROM icon_mapping " 1100 "WHERE page_url=?")); 1101 if (!statement.is_valid()) 1102 return false; 1103 1104 // Do nothing if there are existing bindings 1105 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 1106 if (statement.Step()) 1107 return true; 1108 1109 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 1110 "INSERT INTO icon_mapping (page_url, icon_id) " 1111 "SELECT ?, icon_id FROM icon_mapping " 1112 "WHERE page_url = ?")); 1113 1114 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 1115 statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url)); 1116 return statement.Run(); 1117 } 1118 1119 bool ThumbnailDatabase::InitIconMappingEnumerator( 1120 chrome::IconType type, 1121 IconMappingEnumerator* enumerator) { 1122 DCHECK(!enumerator->statement_.is_valid()); 1123 enumerator->statement_.Assign(db_.GetCachedStatement( 1124 SQL_FROM_HERE, 1125 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 1126 "favicons.url, icon_mapping.page_url " 1127 "FROM icon_mapping JOIN favicons ON (" 1128 "icon_mapping.icon_id = favicons.id) " 1129 "WHERE favicons.icon_type = ?")); 1130 enumerator->statement_.BindInt(0, type); 1131 return enumerator->statement_.is_valid(); 1132 } 1133 1134 bool ThumbnailDatabase::RetainDataForPageUrls( 1135 const std::vector<GURL>& urls_to_keep) { 1136 sql::Transaction transaction(&db_); 1137 if (!transaction.Begin()) 1138 return false; 1139 1140 // temp.icon_id_mapping generates new icon ids as consecutive 1141 // integers starting from 1, and maps them to the old icon ids. 1142 { 1143 const char kIconMappingCreate[] = 1144 "CREATE TEMP TABLE icon_id_mapping " 1145 "(" 1146 "new_icon_id INTEGER PRIMARY KEY," 1147 "old_icon_id INTEGER NOT NULL UNIQUE" 1148 ")"; 1149 if (!db_.Execute(kIconMappingCreate)) 1150 return false; 1151 1152 // Insert the icon ids for retained urls, skipping duplicates. 1153 const char kIconMappingSql[] = 1154 "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " 1155 "SELECT icon_id FROM icon_mapping WHERE page_url = ?"; 1156 sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql)); 1157 for (std::vector<GURL>::const_iterator 1158 i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) { 1159 statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i)); 1160 if (!statement.Run()) 1161 return false; 1162 statement.Reset(true); 1163 } 1164 } 1165 1166 const char kRenameIconMappingTable[] = 1167 "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; 1168 const char kCopyIconMapping[] = 1169 "INSERT INTO icon_mapping (page_url, icon_id) " 1170 "SELECT old.page_url, mapping.new_icon_id " 1171 "FROM old_icon_mapping AS old " 1172 "JOIN temp.icon_id_mapping AS mapping " 1173 "ON (old.icon_id = mapping.old_icon_id)"; 1174 const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; 1175 1176 const char kRenameFaviconsTable[] = 1177 "ALTER TABLE favicons RENAME TO old_favicons"; 1178 const char kCopyFavicons[] = 1179 "INSERT INTO favicons (id, url, icon_type) " 1180 "SELECT mapping.new_icon_id, old.url, old.icon_type " 1181 "FROM old_favicons AS old " 1182 "JOIN temp.icon_id_mapping AS mapping " 1183 "ON (old.id = mapping.old_icon_id)"; 1184 const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; 1185 1186 const char kRenameFaviconBitmapsTable[] = 1187 "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; 1188 const char kCopyFaviconBitmaps[] = 1189 "INSERT INTO favicon_bitmaps " 1190 " (icon_id, last_updated, image_data, width, height) " 1191 "SELECT mapping.new_icon_id, old.last_updated, " 1192 " old.image_data, old.width, old.height " 1193 "FROM old_favicon_bitmaps AS old " 1194 "JOIN temp.icon_id_mapping AS mapping " 1195 "ON (old.icon_id = mapping.old_icon_id)"; 1196 const char kDropOldFaviconBitmapsTable[] = 1197 "DROP TABLE old_favicon_bitmaps"; 1198 1199 // Rename existing tables to new location. 1200 if (!db_.Execute(kRenameIconMappingTable) || 1201 !db_.Execute(kRenameFaviconsTable) || 1202 !db_.Execute(kRenameFaviconBitmapsTable)) { 1203 return false; 1204 } 1205 1206 // Initialize the replacement tables. At this point the old indices 1207 // still exist (pointing to the old_* tables), so do not initialize 1208 // the indices. 1209 if (!InitTables(&db_)) 1210 return false; 1211 1212 // Copy all of the data over. 1213 if (!db_.Execute(kCopyIconMapping) || 1214 !db_.Execute(kCopyFavicons) || 1215 !db_.Execute(kCopyFaviconBitmaps)) { 1216 return false; 1217 } 1218 1219 // Drop the old_* tables, which also drops the indices. 1220 if (!db_.Execute(kDropOldIconMappingTable) || 1221 !db_.Execute(kDropOldFaviconsTable) || 1222 !db_.Execute(kDropOldFaviconBitmapsTable)) { 1223 return false; 1224 } 1225 1226 // Recreate the indices. 1227 // TODO(shess): UNIQUE indices could fail due to duplication. This 1228 // could happen in case of corruption. 1229 if (!InitIndices(&db_)) 1230 return false; 1231 1232 const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; 1233 if (!db_.Execute(kIconMappingDrop)) 1234 return false; 1235 1236 return transaction.Commit(); 1237 } 1238 1239 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, 1240 const base::FilePath& db_name) { 1241 size_t startup_kb = 0; 1242 int64 size_64; 1243 if (base::GetFileSize(db_name, &size_64)) 1244 startup_kb = static_cast<size_t>(size_64 / 1024); 1245 1246 db->set_histogram_tag("Thumbnail"); 1247 db->set_error_callback(base::Bind(&DatabaseErrorCallback, 1248 db, db_name, startup_kb)); 1249 1250 // Thumbnails db now only stores favicons, so we don't need that big a page 1251 // size or cache. 1252 db->set_page_size(2048); 1253 db->set_cache_size(32); 1254 1255 // Run the database in exclusive mode. Nobody else should be accessing the 1256 // database while we're running, and this will give somewhat improved perf. 1257 db->set_exclusive_locking(); 1258 1259 if (!db->Open(db_name)) 1260 return sql::INIT_FAILURE; 1261 1262 return sql::INIT_OK; 1263 } 1264 1265 sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) { 1266 sql::InitStatus status = OpenDatabase(&db_, db_name); 1267 if (status != sql::INIT_OK) 1268 return status; 1269 1270 // Clear databases which are too old to process. 1271 DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber); 1272 sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber); 1273 1274 // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and 1275 // 25. Future versions are not destroyed because that could lead to 1276 // data loss if the profile is opened by a later channel, but 1277 // perhaps a heuristic like >kCurrentVersionNumber+3 could be used. 1278 1279 // Scope initialization in a transaction so we can't be partially initialized. 1280 sql::Transaction transaction(&db_); 1281 if (!transaction.Begin()) 1282 return sql::INIT_FAILURE; 1283 1284 // TODO(shess): Failing Begin() implies that something serious is 1285 // wrong with the database. Raze() may be in order. 1286 1287 #if defined(OS_MACOSX) 1288 // Exclude the thumbnails file from backups. 1289 base::mac::SetFileBackupExclusion(db_name); 1290 #endif 1291 1292 // thumbnails table has been obsolete for a long time, remove any 1293 // detrious. 1294 ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails")); 1295 1296 // At some point, operations involving temporary tables weren't done 1297 // atomically and users have been stranded. Drop those tables and 1298 // move on. 1299 // TODO(shess): Prove it? Audit all cases and see if it's possible 1300 // that this implies non-atomic update, and should thus be handled 1301 // via the corruption handler. 1302 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons")); 1303 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps")); 1304 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping")); 1305 1306 // Create the tables. 1307 if (!meta_table_.Init(&db_, kCurrentVersionNumber, 1308 kCompatibleVersionNumber) || 1309 !InitTables(&db_) || 1310 !InitIndices(&db_)) { 1311 return sql::INIT_FAILURE; 1312 } 1313 1314 // Version check. We should not encounter a database too old for us to handle 1315 // in the wild, so we try to continue in that case. 1316 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { 1317 LOG(WARNING) << "Thumbnail database is too new."; 1318 return sql::INIT_TOO_NEW; 1319 } 1320 1321 int cur_version = meta_table_.GetVersionNumber(); 1322 1323 if (!db_.DoesColumnExist("favicons", "icon_type")) { 1324 LOG(ERROR) << "Raze because of missing favicon.icon_type"; 1325 RecordInvalidStructure(STRUCTURE_EVENT_VERSION4); 1326 1327 db_.RazeAndClose(); 1328 return sql::INIT_FAILURE; 1329 } 1330 1331 if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) { 1332 LOG(ERROR) << "Raze because of missing favicon.sizes"; 1333 RecordInvalidStructure(STRUCTURE_EVENT_VERSION5); 1334 1335 db_.RazeAndClose(); 1336 return sql::INIT_FAILURE; 1337 } 1338 1339 if (cur_version == 5) { 1340 ++cur_version; 1341 if (!UpgradeToVersion6()) 1342 return CantUpgradeToVersion(cur_version); 1343 } 1344 1345 if (cur_version == 6) { 1346 ++cur_version; 1347 if (!UpgradeToVersion7()) 1348 return CantUpgradeToVersion(cur_version); 1349 } 1350 1351 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << 1352 "Thumbnail database version " << cur_version << " is too old to handle."; 1353 1354 // Initialization is complete. 1355 if (!transaction.Commit()) 1356 return sql::INIT_FAILURE; 1357 1358 // Raze the database if the structure of the favicons database is not what 1359 // it should be. This error cannot be detected via the SQL error code because 1360 // the error code for running SQL statements against a database with missing 1361 // columns is SQLITE_ERROR which is not unique enough to act upon. 1362 // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. 1363 // (crbug.com/166453) 1364 if (IsFaviconDBStructureIncorrect()) { 1365 LOG(ERROR) << "Raze because of invalid favicon db structure."; 1366 RecordInvalidStructure(STRUCTURE_EVENT_FAVICON); 1367 1368 db_.RazeAndClose(); 1369 return sql::INIT_FAILURE; 1370 } 1371 1372 return sql::INIT_OK; 1373 } 1374 1375 sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) { 1376 LOG(WARNING) << "Unable to update to thumbnail database to version " << 1377 cur_version << "."; 1378 db_.Close(); 1379 return sql::INIT_FAILURE; 1380 } 1381 1382 bool ThumbnailDatabase::UpgradeToVersion6() { 1383 // Move bitmap data from favicons to favicon_bitmaps. 1384 bool success = 1385 db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, " 1386 "image_data, width, height)" 1387 "SELECT id, last_updated, image_data, 0, 0 FROM favicons") && 1388 db_.Execute("CREATE TABLE temp_favicons (" 1389 "id INTEGER PRIMARY KEY," 1390 "url LONGVARCHAR NOT NULL," 1391 "icon_type INTEGER DEFAULT 1," 1392 // default icon_type FAVICON to be consistent with 1393 // past migration. 1394 "sizes LONGVARCHAR)") && 1395 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1396 "SELECT id, url, icon_type FROM favicons") && 1397 db_.Execute("DROP TABLE favicons") && 1398 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"); 1399 // NOTE(shess): v7 will re-create the index. 1400 if (!success) 1401 return false; 1402 1403 meta_table_.SetVersionNumber(6); 1404 meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber)); 1405 return true; 1406 } 1407 1408 bool ThumbnailDatabase::UpgradeToVersion7() { 1409 // Sizes column was never used, remove it. 1410 bool success = 1411 db_.Execute("CREATE TABLE temp_favicons (" 1412 "id INTEGER PRIMARY KEY," 1413 "url LONGVARCHAR NOT NULL," 1414 // default icon_type FAVICON to be consistent with 1415 // past migration. 1416 "icon_type INTEGER DEFAULT 1)") && 1417 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1418 "SELECT id, url, icon_type FROM favicons") && 1419 db_.Execute("DROP TABLE favicons") && 1420 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") && 1421 db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"); 1422 1423 if (!success) 1424 return false; 1425 1426 meta_table_.SetVersionNumber(7); 1427 meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber)); 1428 return true; 1429 } 1430 1431 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { 1432 return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); 1433 } 1434 1435 } // namespace history 1436