OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "sql/recovery.h" | 5 #include "sql/recovery.h" |
6 | 6 |
7 #include "base/files/file_path.h" | 7 #include "base/files/file_path.h" |
8 #include "base/format_macros.h" | 8 #include "base/format_macros.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/metrics/histogram.h" |
10 #include "base/metrics/sparse_histogram.h" | 11 #include "base/metrics/sparse_histogram.h" |
11 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
12 #include "base/strings/stringprintf.h" | 13 #include "base/strings/stringprintf.h" |
13 #include "sql/connection.h" | 14 #include "sql/connection.h" |
14 #include "sql/statement.h" | 15 #include "sql/statement.h" |
15 #include "third_party/sqlite/sqlite3.h" | 16 #include "third_party/sqlite/sqlite3.h" |
16 | 17 |
17 namespace sql { | 18 namespace sql { |
18 | 19 |
| 20 namespace { |
| 21 |
| 22 enum RecoveryEventType { |
| 23 // Failed to open temporary database to recover into. |
| 24 RECOVERY_FAILED_OPEN_TEMPORARY = 0, |
| 25 |
| 26 // Failed to initialize recover vtable system. |
| 27 RECOVERY_FAILED_VIRTUAL_TABLE_INIT, |
| 28 |
| 29 // System SQLite doesn't support vtable. |
| 30 RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE, |
| 31 |
| 32 // Failed attempting to enable writable_schema. |
| 33 RECOVERY_FAILED_WRITABLE_SCHEMA, |
| 34 |
| 35 // Failed to attach the corrupt database to the temporary database. |
| 36 RECOVERY_FAILED_ATTACH, |
| 37 |
| 38 // Failed sqlite3_backup_init(). Error code in Sqlite.RecoveryHandle. |
| 39 RECOVERY_FAILED_BACKUP_INIT, |
| 40 |
| 41 // Failed sqlite3_backup_step(). Error code in Sqlite.RecoveryStep. |
| 42 RECOVERY_FAILED_BACKUP_STEP, |
| 43 |
| 44 // The target table contained a type which the code is not equipped |
| 45 // to handle. This should only happen if things are fubar. |
| 46 RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE, |
| 47 |
| 48 // The target table does not exist. |
| 49 RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE, |
| 50 |
| 51 // The recovery virtual table creation failed. |
| 52 RECOVERY_FAILED_AUTORECOVER_CREATE, |
| 53 |
| 54 // Copying data from the recovery table to the target table failed. |
| 55 RECOVERY_FAILED_AUTORECOVER_INSERT, |
| 56 |
| 57 // Dropping the recovery virtual table failed. |
| 58 RECOVERY_FAILED_AUTORECOVER_DROP, |
| 59 |
| 60 // Failure creating recovery meta table. |
| 61 RECOVERY_FAILED_META_CREATE, |
| 62 |
| 63 // Failed in querying recovery meta table. |
| 64 RECOVERY_FAILED_META_QUERY, |
| 65 |
| 66 // No version key in recovery meta table. |
| 67 RECOVERY_FAILED_META_NO_VERSION, |
| 68 |
| 69 // Always keep this at the end. |
| 70 RECOVERY_EVENT_MAX, |
| 71 }; |
| 72 |
| 73 void RecordRecoveryEvent(RecoveryEventType recovery_event) { |
| 74 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryFailures", |
| 75 recovery_event, RECOVERY_EVENT_MAX); |
| 76 } |
| 77 |
| 78 } // namespace |
| 79 |
19 // static | 80 // static |
20 bool Recovery::FullRecoverySupported() { | 81 bool Recovery::FullRecoverySupported() { |
21 // TODO(shess): See comment in Init(). | 82 // TODO(shess): See comment in Init(). |
22 #if defined(USE_SYSTEM_SQLITE) | 83 #if defined(USE_SYSTEM_SQLITE) |
23 return false; | 84 return false; |
24 #else | 85 #else |
25 return true; | 86 return true; |
26 #endif | 87 #endif |
27 } | 88 } |
28 | 89 |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
95 // next database access, so immediately force an access. Enabling | 156 // next database access, so immediately force an access. Enabling |
96 // writable_schema allows processing through certain kinds of | 157 // writable_schema allows processing through certain kinds of |
97 // corruption. | 158 // corruption. |
98 // TODO(shess): It would be better to just close the handle, but it | 159 // TODO(shess): It would be better to just close the handle, but it |
99 // is necessary for the final backup which rewrites things. It | 160 // is necessary for the final backup which rewrites things. It |
100 // might be reasonable to close then re-open the handle. | 161 // might be reasonable to close then re-open the handle. |
101 ignore_result(db_->Execute("PRAGMA writable_schema=1")); | 162 ignore_result(db_->Execute("PRAGMA writable_schema=1")); |
102 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); | 163 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); |
103 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); | 164 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); |
104 | 165 |
105 if (!recover_db_.OpenTemporary()) | 166 // TODO(shess): If this is a common failure case, it might be |
| 167 // possible to fall back to a memory database. But it probably |
| 168 // implies that the SQLite tmpdir logic is busted, which could cause |
| 169 // a variety of other random issues in our code. |
| 170 if (!recover_db_.OpenTemporary()) { |
| 171 RecordRecoveryEvent(RECOVERY_FAILED_OPEN_TEMPORARY); |
106 return false; | 172 return false; |
| 173 } |
107 | 174 |
108 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The | 175 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The |
109 // virtual table implementation relies on SQLite internals for some | 176 // virtual table implementation relies on SQLite internals for some |
110 // types and functions, which could be copied inline to make it | 177 // types and functions, which could be copied inline to make it |
111 // standalone. Or an alternate implementation could try to read | 178 // standalone. Or an alternate implementation could try to read |
112 // through errors entirely at the SQLite level. | 179 // through errors entirely at the SQLite level. |
113 // | 180 // |
114 // For now, defer to the caller. The setup will succeed, but the | 181 // For now, defer to the caller. The setup will succeed, but the |
115 // later CREATE VIRTUAL TABLE call will fail, at which point the | 182 // later CREATE VIRTUAL TABLE call will fail, at which point the |
116 // caller can fire Unrecoverable(). | 183 // caller can fire Unrecoverable(). |
117 #if !defined(USE_SYSTEM_SQLITE) | 184 #if !defined(USE_SYSTEM_SQLITE) |
118 int rc = recoverVtableInit(recover_db_.db_); | 185 int rc = recoverVtableInit(recover_db_.db_); |
119 if (rc != SQLITE_OK) { | 186 if (rc != SQLITE_OK) { |
| 187 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT); |
120 LOG(ERROR) << "Failed to initialize recover module: " | 188 LOG(ERROR) << "Failed to initialize recover module: " |
121 << recover_db_.GetErrorMessage(); | 189 << recover_db_.GetErrorMessage(); |
122 return false; | 190 return false; |
123 } | 191 } |
| 192 #else |
| 193 // If this is infrequent enough, just wire it to Raze(). |
| 194 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE); |
124 #endif | 195 #endif |
125 | 196 |
126 // Turn on |SQLITE_RecoveryMode| for the handle, which allows | 197 // Turn on |SQLITE_RecoveryMode| for the handle, which allows |
127 // reading certain broken databases. | 198 // reading certain broken databases. |
128 if (!recover_db_.Execute("PRAGMA writable_schema=1")) | 199 if (!recover_db_.Execute("PRAGMA writable_schema=1")) { |
| 200 RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA); |
129 return false; | 201 return false; |
| 202 } |
130 | 203 |
131 if (!recover_db_.AttachDatabase(db_path, "corrupt")) | 204 if (!recover_db_.AttachDatabase(db_path, "corrupt")) { |
| 205 RecordRecoveryEvent(RECOVERY_FAILED_ATTACH); |
132 return false; | 206 return false; |
| 207 } |
133 | 208 |
134 return true; | 209 return true; |
135 } | 210 } |
136 | 211 |
137 bool Recovery::Backup() { | 212 bool Recovery::Backup() { |
138 CHECK(db_); | 213 CHECK(db_); |
139 CHECK(recover_db_.is_open()); | 214 CHECK(recover_db_.is_open()); |
140 | 215 |
141 // TODO(shess): Some of the failure cases here may need further | 216 // TODO(shess): Some of the failure cases here may need further |
142 // exploration. Just as elsewhere, persistent problems probably | 217 // exploration. Just as elsewhere, persistent problems probably |
(...skipping 23 matching lines...) Expand all Loading... |
166 // and attempt the backup again. | 241 // and attempt the backup again. |
167 // | 242 // |
168 // For now, this code attempts a best effort and records histograms | 243 // For now, this code attempts a best effort and records histograms |
169 // to inform future development. | 244 // to inform future development. |
170 | 245 |
171 // Backup the original db from the recovered db. | 246 // Backup the original db from the recovered db. |
172 const char* kMain = "main"; | 247 const char* kMain = "main"; |
173 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, | 248 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, |
174 recover_db_.db_, kMain); | 249 recover_db_.db_, kMain); |
175 if (!backup) { | 250 if (!backup) { |
| 251 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT); |
| 252 |
176 // Error code is in the destination database handle. | 253 // Error code is in the destination database handle. |
177 int err = sqlite3_errcode(db_->db_); | 254 int err = sqlite3_extended_errcode(db_->db_); |
178 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); | 255 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); |
179 LOG(ERROR) << "sqlite3_backup_init() failed: " | 256 LOG(ERROR) << "sqlite3_backup_init() failed: " |
180 << sqlite3_errmsg(db_->db_); | 257 << sqlite3_errmsg(db_->db_); |
| 258 |
181 return false; | 259 return false; |
182 } | 260 } |
183 | 261 |
184 // -1 backs up the entire database. | 262 // -1 backs up the entire database. |
185 int rc = sqlite3_backup_step(backup, -1); | 263 int rc = sqlite3_backup_step(backup, -1); |
186 int pages = sqlite3_backup_pagecount(backup); | 264 int pages = sqlite3_backup_pagecount(backup); |
187 // TODO(shess): sqlite3_backup_finish() appears to allow returning a | 265 // TODO(shess): sqlite3_backup_finish() appears to allow returning a |
188 // different value from sqlite3_backup_step(). Circle back and | 266 // different value from sqlite3_backup_step(). Circle back and |
189 // figure out if that can usefully inform the decision of whether to | 267 // figure out if that can usefully inform the decision of whether to |
190 // retry or not. | 268 // retry or not. |
191 sqlite3_backup_finish(backup); | 269 sqlite3_backup_finish(backup); |
192 DCHECK_GT(pages, 0); | 270 DCHECK_GT(pages, 0); |
193 | 271 |
194 if (rc != SQLITE_DONE) { | 272 if (rc != SQLITE_DONE) { |
| 273 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP); |
195 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); | 274 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); |
196 LOG(ERROR) << "sqlite3_backup_step() failed: " | 275 LOG(ERROR) << "sqlite3_backup_step() failed: " |
197 << sqlite3_errmsg(db_->db_); | 276 << sqlite3_errmsg(db_->db_); |
198 } | 277 } |
199 | 278 |
200 // The destination database was locked. Give up, but leave the data | 279 // The destination database was locked. Give up, but leave the data |
201 // in place. Maybe it won't be locked next time. | 280 // in place. Maybe it won't be locked next time. |
202 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { | 281 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { |
203 Shutdown(POISON); | 282 Shutdown(POISON); |
204 return false; | 283 return false; |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 column_decl += " BLOB"; | 378 column_decl += " BLOB"; |
300 } else { | 379 } else { |
301 // TODO(shess): AFAICT, there remain: | 380 // TODO(shess): AFAICT, there remain: |
302 // - contains("CLOB") -> TEXT | 381 // - contains("CLOB") -> TEXT |
303 // - contains("REAL") -> REAL | 382 // - contains("REAL") -> REAL |
304 // - contains("FLOA") -> REAL | 383 // - contains("FLOA") -> REAL |
305 // - contains("DOUB") -> REAL | 384 // - contains("DOUB") -> REAL |
306 // - other -> "NUMERIC" | 385 // - other -> "NUMERIC" |
307 // Just code those in as they come up. | 386 // Just code those in as they come up. |
308 NOTREACHED() << " Unsupported type " << column_type; | 387 NOTREACHED() << " Unsupported type " << column_type; |
| 388 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE); |
309 return false; | 389 return false; |
310 } | 390 } |
311 | 391 |
312 // If column has constraint "NOT NULL", then inserting NULL into | 392 // If column has constraint "NOT NULL", then inserting NULL into |
313 // that column will fail. If the column has a non-NULL DEFAULT | 393 // that column will fail. If the column has a non-NULL DEFAULT |
314 // specified, the INSERT will handle it (see below). If the | 394 // specified, the INSERT will handle it (see below). If the |
315 // DEFAULT is also NULL, the row must be filtered out. | 395 // DEFAULT is also NULL, the row must be filtered out. |
316 // TODO(shess): The above scenario applies to INSERT OR REPLACE, | 396 // TODO(shess): The above scenario applies to INSERT OR REPLACE, |
317 // whereas INSERT OR IGNORE drops such rows. | 397 // whereas INSERT OR IGNORE drops such rows. |
318 // http://www.sqlite.org/lang_conflict.html | 398 // http://www.sqlite.org/lang_conflict.html |
(...skipping 10 matching lines...) Expand all Loading... |
329 } else { | 409 } else { |
330 // The default value appears to be pre-quoted, as if it is | 410 // The default value appears to be pre-quoted, as if it is |
331 // literally from the sqlite_master CREATE statement. | 411 // literally from the sqlite_master CREATE statement. |
332 std::string default_value = s.ColumnString(4); | 412 std::string default_value = s.ColumnString(4); |
333 insert_columns.push_back(base::StringPrintf( | 413 insert_columns.push_back(base::StringPrintf( |
334 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); | 414 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); |
335 } | 415 } |
336 } | 416 } |
337 | 417 |
338 // Receiving no column information implies that the table doesn't exist. | 418 // Receiving no column information implies that the table doesn't exist. |
339 if (create_column_decls.empty()) | 419 if (create_column_decls.empty()) { |
| 420 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE); |
340 return false; | 421 return false; |
| 422 } |
341 | 423 |
342 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. | 424 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. |
343 if (pk_column_count == 1 && !rowid_decl.empty()) | 425 if (pk_column_count == 1 && !rowid_decl.empty()) |
344 create_column_decls[rowid_ofs] = rowid_decl; | 426 create_column_decls[rowid_ofs] = rowid_decl; |
345 | 427 |
346 // Additional columns accept anything. | 428 // Additional columns accept anything. |
347 // TODO(shess): ignoreN isn't well namespaced. But it will fail to | 429 // TODO(shess): ignoreN isn't well namespaced. But it will fail to |
348 // execute in case of conflicts. | 430 // execute in case of conflicts. |
349 for (size_t i = 0; i < extend_columns; ++i) { | 431 for (size_t i = 0; i < extend_columns; ++i) { |
350 create_column_decls.push_back( | 432 create_column_decls.push_back( |
351 base::StringPrintf("ignore%" PRIuS " ANY", i)); | 433 base::StringPrintf("ignore%" PRIuS " ANY", i)); |
352 } | 434 } |
353 | 435 |
354 std::string recover_create(base::StringPrintf( | 436 std::string recover_create(base::StringPrintf( |
355 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", | 437 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", |
356 table_name, | 438 table_name, |
357 table_name, | 439 table_name, |
358 JoinString(create_column_decls, ',').c_str())); | 440 JoinString(create_column_decls, ',').c_str())); |
359 | 441 |
360 std::string recover_insert(base::StringPrintf( | 442 std::string recover_insert(base::StringPrintf( |
361 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", | 443 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", |
362 table_name, | 444 table_name, |
363 JoinString(insert_columns, ',').c_str(), | 445 JoinString(insert_columns, ',').c_str(), |
364 table_name)); | 446 table_name)); |
365 | 447 |
366 std::string recover_drop(base::StringPrintf( | 448 std::string recover_drop(base::StringPrintf( |
367 "DROP TABLE temp.recover_%s", table_name)); | 449 "DROP TABLE temp.recover_%s", table_name)); |
368 | 450 |
369 if (!db()->Execute(recover_create.c_str())) | 451 if (!db()->Execute(recover_create.c_str())) { |
| 452 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE); |
370 return false; | 453 return false; |
| 454 } |
371 | 455 |
372 if (!db()->Execute(recover_insert.c_str())) { | 456 if (!db()->Execute(recover_insert.c_str())) { |
| 457 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT); |
373 ignore_result(db()->Execute(recover_drop.c_str())); | 458 ignore_result(db()->Execute(recover_drop.c_str())); |
374 return false; | 459 return false; |
375 } | 460 } |
376 | 461 |
377 *rows_recovered = db()->GetLastChangeCount(); | 462 *rows_recovered = db()->GetLastChangeCount(); |
378 | 463 |
379 // TODO(shess): Is leaving the recover table around a breaker? | 464 // TODO(shess): Is leaving the recover table around a breaker? |
380 return db()->Execute(recover_drop.c_str()); | 465 if (!db()->Execute(recover_drop.c_str())) { |
| 466 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_DROP); |
| 467 return false; |
| 468 } |
| 469 return true; |
381 } | 470 } |
382 | 471 |
383 bool Recovery::SetupMeta() { | 472 bool Recovery::SetupMeta() { |
384 const char kCreateSql[] = | 473 const char kCreateSql[] = |
385 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" | 474 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" |
386 "(" | 475 "(" |
387 "corrupt.meta," | 476 "corrupt.meta," |
388 "key TEXT NOT NULL," | 477 "key TEXT NOT NULL," |
389 "value ANY" // Whatever is stored. | 478 "value ANY" // Whatever is stored. |
390 ")"; | 479 ")"; |
391 return db()->Execute(kCreateSql); | 480 if (!db()->Execute(kCreateSql)) { |
| 481 RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE); |
| 482 return false; |
| 483 } |
| 484 return true; |
392 } | 485 } |
393 | 486 |
394 bool Recovery::GetMetaVersionNumber(int* version) { | 487 bool Recovery::GetMetaVersionNumber(int* version) { |
395 DCHECK(version); | 488 DCHECK(version); |
396 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); | 489 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); |
397 // Unfortunately, DoesTableExist() queries sqlite_master, not | 490 // Unfortunately, DoesTableExist() queries sqlite_master, not |
398 // sqlite_temp_master. | 491 // sqlite_temp_master. |
399 | 492 |
400 const char kVersionSql[] = | 493 const char kVersionSql[] = |
401 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; | 494 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; |
402 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); | 495 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); |
403 if (!recovery_version.Step()) | 496 if (!recovery_version.Step()) { |
| 497 if (!recovery_version.Succeeded()) { |
| 498 RecordRecoveryEvent(RECOVERY_FAILED_META_QUERY); |
| 499 } else { |
| 500 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION); |
| 501 } |
404 return false; | 502 return false; |
| 503 } |
405 | 504 |
406 *version = recovery_version.ColumnInt(0); | 505 *version = recovery_version.ColumnInt(0); |
407 return true; | 506 return true; |
408 } | 507 } |
409 | 508 |
410 } // namespace sql | 509 } // namespace sql |
OLD | NEW |