• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@chromium.org on 2013-07-01
79 // Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20
80 // Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12
81 // Version 4: 5f104d76/r77288 by sky@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 
FillIconMapping(const sql::Statement & statement,const GURL & page_url,history::IconMapping * icon_mapping)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 
RecordInvalidStructure(InvalidStructureType invalid_type)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.
DumpWithoutCrashing2000(const std::string & debug_info)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 
ReportCorrupt(sql::Connection * db,size_t startup_kb)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 
ReportError(sql::Connection * db,int error)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}.
GenerateDiagnostics(sql::Connection * db,size_t startup_kb,int extended_error)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()|.
InitTables(sql::Connection * db)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()|.
InitIndices(sql::Connection * db)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 
RecordRecoveryEvent(RecoveryEventType recovery_event)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.
RecoverDatabaseOrRaze(sql::Connection * db,const base::FilePath & db_path)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 
DatabaseErrorCallback(sql::Connection * db,const base::FilePath & db_path,size_t startup_kb,int extended_error,sql::Statement * stmt)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 
IconMappingEnumerator()577 ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() {
578 }
579 
~IconMappingEnumerator()580 ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() {
581 }
582 
GetNextIconMapping(IconMapping * icon_mapping)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 
ThumbnailDatabase()591 ThumbnailDatabase::ThumbnailDatabase() {
592 }
593 
~ThumbnailDatabase()594 ThumbnailDatabase::~ThumbnailDatabase() {
595   // The DBCloseScoper will delete the DB and the cache.
596 }
597 
Init(const base::FilePath & db_name)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 
ComputeDatabaseMetrics()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 
BeginTransaction()626 void ThumbnailDatabase::BeginTransaction() {
627   db_.BeginTransaction();
628 }
629 
CommitTransaction()630 void ThumbnailDatabase::CommitTransaction() {
631   db_.CommitTransaction();
632 }
633 
RollbackTransaction()634 void ThumbnailDatabase::RollbackTransaction() {
635   db_.RollbackTransaction();
636 }
637 
Vacuum()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 
TrimMemory(bool aggressively)644 void ThumbnailDatabase::TrimMemory(bool aggressively) {
645   db_.TrimMemory(aggressively);
646 }
647 
GetFaviconBitmapIDSizes(favicon_base::FaviconID icon_id,std::vector<FaviconBitmapIDSize> * bitmap_id_sizes)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 
GetFaviconBitmaps(favicon_base::FaviconID icon_id,std::vector<FaviconBitmap> * favicon_bitmaps)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 
GetFaviconBitmap(FaviconBitmapID bitmap_id,base::Time * last_updated,scoped_refptr<base::RefCountedMemory> * png_icon_data,gfx::Size * pixel_size)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 
AddFaviconBitmap(favicon_base::FaviconID icon_id,const scoped_refptr<base::RefCountedMemory> & icon_data,base::Time time,const gfx::Size & pixel_size)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 
SetFaviconBitmap(FaviconBitmapID bitmap_id,scoped_refptr<base::RefCountedMemory> bitmap_data,base::Time time)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 
SetFaviconBitmapLastUpdateTime(FaviconBitmapID bitmap_id,base::Time time)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 
DeleteFaviconBitmap(FaviconBitmapID bitmap_id)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 
SetFaviconOutOfDate(favicon_base::FaviconID icon_id)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 
GetFaviconIDForFaviconURL(const GURL & icon_url,int required_icon_type,favicon_base::IconType * icon_type)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 
GetFaviconHeader(favicon_base::FaviconID icon_id,GURL * icon_url,favicon_base::IconType * icon_type)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 
AddFavicon(const GURL & icon_url,favicon_base::IconType icon_type)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 
AddFavicon(const GURL & icon_url,favicon_base::IconType icon_type,const scoped_refptr<base::RefCountedMemory> & icon_data,base::Time time,const gfx::Size & pixel_size)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 
DeleteFavicon(favicon_base::FaviconID id)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 
GetIconMappingsForPageURL(const GURL & page_url,int required_icon_types,std::vector<IconMapping> * filtered_mapping_data)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 
GetIconMappingsForPageURL(const GURL & page_url,std::vector<IconMapping> * mapping_data)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 
AddIconMapping(const GURL & page_url,favicon_base::FaviconID icon_id)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 
UpdateIconMapping(IconMappingID mapping_id,favicon_base::FaviconID icon_id)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 
DeleteIconMappings(const GURL & page_url)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 
DeleteIconMapping(IconMappingID mapping_id)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 
HasMappingFor(favicon_base::FaviconID id)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 
CloneIconMappings(const GURL & old_page_url,const GURL & new_page_url)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 
InitIconMappingEnumerator(favicon_base::IconType type,IconMappingEnumerator * enumerator)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 
RetainDataForPageUrls(const std::vector<GURL> & urls_to_keep)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 
OpenDatabase(sql::Connection * db,const base::FilePath & db_name)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 
InitImpl(const base::FilePath & db_name)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 
CantUpgradeToVersion(int cur_version)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 
UpgradeToVersion6()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 
UpgradeToVersion7()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 
IsFaviconDBStructureIncorrect()1321 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() {
1322   return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons");
1323 }
1324 
1325 }  // namespace history
1326