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 "components/webdata/common/web_database.h"
6
7 #include <algorithm>
8
9 #include "base/logging.h"
10 #include "base/stl_util.h"
11 #include "sql/transaction.h"
12
13 // Current version number. Note: when changing the current version number,
14 // corresponding changes must happen in the unit tests, and new migration test
15 // added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
16 // static
17 const int WebDatabase::kCurrentVersionNumber = 90;
18
19 const int WebDatabase::kDeprecatedVersionNumber = 51;
20
21 const base::FilePath::CharType WebDatabase::kInMemoryPath[] =
22 FILE_PATH_LITERAL(":memory");
23
24 namespace {
25
26 const int kCompatibleVersionNumber = 83;
27
28 // Change the version number and possibly the compatibility version of
29 // |meta_table_|.
ChangeVersion(sql::MetaTable * meta_table,int version_num,bool update_compatible_version_num)30 void ChangeVersion(sql::MetaTable* meta_table,
31 int version_num,
32 bool update_compatible_version_num) {
33 meta_table->SetVersionNumber(version_num);
34 if (update_compatible_version_num) {
35 meta_table->SetCompatibleVersionNumber(
36 std::min(version_num, kCompatibleVersionNumber));
37 }
38 }
39
40 // Outputs the failed version number as a warning and always returns
41 // |sql::INIT_FAILURE|.
FailedMigrationTo(int version_num)42 sql::InitStatus FailedMigrationTo(int version_num) {
43 LOG(WARNING) << "Unable to update web database to version " << version_num
44 << ".";
45 NOTREACHED();
46 return sql::INIT_FAILURE;
47 }
48
49 } // namespace
50
WebDatabase()51 WebDatabase::WebDatabase()
52 : db_({// Run the database in exclusive mode. Nobody else should be
53 // accessing the database while we're running, and this will give
54 // somewhat improved perf.
55 .exclusive_locking = true,
56 // We don't store that much data in the tables so use a small page
57 // size. This provides a large benefit for empty tables (which is
58 // very likely with the tables we create).
59 .page_size = 2048,
60 // We shouldn't have much data and what access we currently have is
61 // quite infrequent. So we go with a small cache size.
62 .cache_size = 32}) {}
63
~WebDatabase()64 WebDatabase::~WebDatabase() {}
65
AddTable(WebDatabaseTable * table)66 void WebDatabase::AddTable(WebDatabaseTable* table) {
67 tables_[table->GetTypeKey()] = table;
68 }
69
GetTable(WebDatabaseTable::TypeKey key)70 WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
71 return tables_[key];
72 }
73
BeginTransaction()74 void WebDatabase::BeginTransaction() {
75 db_.BeginTransaction();
76 }
77
CommitTransaction()78 void WebDatabase::CommitTransaction() {
79 db_.CommitTransaction();
80 }
81
GetDiagnosticInfo(int extended_error,sql::Statement * statement)82 std::string WebDatabase::GetDiagnosticInfo(int extended_error,
83 sql::Statement* statement) {
84 return db_.GetDiagnosticInfo(extended_error, statement);
85 }
86
GetSQLConnection()87 sql::Database* WebDatabase::GetSQLConnection() {
88 return &db_;
89 }
90
Init(const base::FilePath & db_name)91 sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) {
92 db_.set_histogram_tag("Web");
93
94 if ((db_name.value() == kInMemoryPath) ? !db_.OpenInMemory()
95 : !db_.Open(db_name)) {
96 return sql::INIT_FAILURE;
97 }
98
99 // Clobber really old databases.
100 static_assert(kDeprecatedVersionNumber < kCurrentVersionNumber,
101 "Deprecation version must be less than current");
102 sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber);
103
104 // Scope initialization in a transaction so we can't be partially
105 // initialized.
106 sql::Transaction transaction(&db_);
107 if (!transaction.Begin())
108 return sql::INIT_FAILURE;
109
110 // Version check.
111 if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber))
112 return sql::INIT_FAILURE;
113 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
114 LOG(WARNING) << "Web database is too new.";
115 return sql::INIT_TOO_NEW;
116 }
117
118 // Initialize the tables.
119 for (auto it = tables_.begin(); it != tables_.end(); ++it) {
120 it->second->Init(&db_, &meta_table_);
121 }
122
123 // If the file on disk is an older database version, bring it up to date.
124 // If the migration fails we return an error to caller and do not commit
125 // the migration.
126 sql::InitStatus migration_status = MigrateOldVersionsAsNeeded();
127 if (migration_status != sql::INIT_OK)
128 return migration_status;
129
130 // Create the desired SQL tables if they do not already exist.
131 // It's important that this happen *after* the migration code runs.
132 // Otherwise, the migration code would have to explicitly check for empty
133 // tables created in the new format, and skip the migration in that case.
134 for (auto it = tables_.begin(); it != tables_.end(); ++it) {
135 if (!it->second->CreateTablesIfNecessary()) {
136 LOG(WARNING) << "Unable to initialize the web database.";
137 return sql::INIT_FAILURE;
138 }
139 }
140
141 return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
142 }
143
MigrateOldVersionsAsNeeded()144 sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() {
145 // Some malware used to lower the version number, causing migration to
146 // fail. Ensure the version number is at least as high as the compatible
147 // version number.
148 int current_version = std::max(meta_table_.GetVersionNumber(),
149 meta_table_.GetCompatibleVersionNumber());
150 if (current_version > meta_table_.GetVersionNumber())
151 ChangeVersion(&meta_table_, current_version, false);
152
153 DCHECK_GT(current_version, kDeprecatedVersionNumber);
154
155 for (int next_version = current_version + 1;
156 next_version <= kCurrentVersionNumber; ++next_version) {
157 // Do any database-wide migrations.
158 bool update_compatible_version = false;
159 if (!MigrateToVersion(next_version, &update_compatible_version))
160 return FailedMigrationTo(next_version);
161
162 ChangeVersion(&meta_table_, next_version, update_compatible_version);
163
164 // Give each table a chance to migrate to this version.
165 for (auto it = tables_.begin(); it != tables_.end(); ++it) {
166 // Any of the tables may set this to true, but by default it is false.
167 update_compatible_version = false;
168 if (!it->second->MigrateToVersion(next_version,
169 &update_compatible_version)) {
170 return FailedMigrationTo(next_version);
171 }
172
173 ChangeVersion(&meta_table_, next_version, update_compatible_version);
174 }
175 }
176 return sql::INIT_OK;
177 }
178
MigrateToVersion(int version,bool * update_compatible_version)179 bool WebDatabase::MigrateToVersion(int version,
180 bool* update_compatible_version) {
181 // Migrate if necessary.
182 switch (version) {
183 case 58:
184 *update_compatible_version = true;
185 return MigrateToVersion58DropWebAppsAndIntents();
186 case 79:
187 *update_compatible_version = true;
188 return MigrateToVersion79DropLoginsTable();
189 }
190
191 return true;
192 }
193
MigrateToVersion58DropWebAppsAndIntents()194 bool WebDatabase::MigrateToVersion58DropWebAppsAndIntents() {
195 sql::Transaction transaction(&db_);
196 return transaction.Begin() && db_.Execute("DROP TABLE IF EXISTS web_apps") &&
197 db_.Execute("DROP TABLE IF EXISTS web_app_icons") &&
198 db_.Execute("DROP TABLE IF EXISTS web_intents") &&
199 db_.Execute("DROP TABLE IF EXISTS web_intents_defaults") &&
200 transaction.Commit();
201 }
202
MigrateToVersion79DropLoginsTable()203 bool WebDatabase::MigrateToVersion79DropLoginsTable() {
204 sql::Transaction transaction(&db_);
205 return transaction.Begin() &&
206 db_.Execute("DROP TABLE IF EXISTS ie7_logins") &&
207 db_.Execute("DROP TABLE IF EXISTS logins") && transaction.Commit();
208 }
209