Movatterモバイル変換


[0]ホーム

URL:


Google Git
Sign in
chromium /chromium /src /refs/heads/main /. /sql /recovery.cc
blob: b62f124e05eca08947ffaed35a06a64b962010da [file] [log] [blame]
Avi Drissman69b874f2022-09-15 19:11:14[diff] [blame]1// Copyright 2013 The Chromium Authors
shess@chromium.org8d409412013-07-19 18:25:30[diff] [blame]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"sql/recovery.h"
6
avi0b519202015-12-21 07:25:19[diff] [blame]7#include<stddef.h>
8
Victor Costan90dae262021-06-01 21:01:08[diff] [blame]9#include<string>
Avi Drissman70127472022-01-08 23:20:23[diff] [blame]10#include<tuple>
Victor Costan90dae262021-06-01 21:01:08[diff] [blame]11
Takuto Ikuta2eb61342024-05-10 09:05:35[diff] [blame]12#include"base/check.h"
Victor Costan90dae262021-06-01 21:01:08[diff] [blame]13#include"base/check_op.h"
shess@chromium.org8d409412013-07-19 18:25:30[diff] [blame]14#include"base/logging.h"
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]15#include"base/metrics/histogram_functions.h"
Victor Costan90dae262021-06-01 21:01:08[diff] [blame]16#include"base/notreached.h"
Austin Sullivan7b543402023-10-07 00:55:09[diff] [blame]17#include"base/strings/strcat.h"
Austin Sullivan265c201b2023-05-31 02:04:26[diff] [blame]18#include"build/build_config.h"
Victor Costancfbfa602018-08-01 23:24:46[diff] [blame]19#include"sql/database.h"
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]20#include"sql/error_delegate_util.h"
21#include"sql/internal_api_token.h"
22#include"sql/meta_table.h"
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]23#include"sql/sqlite_result_code.h"
shess@chromium.org8d409412013-07-19 18:25:30[diff] [blame]24#include"third_party/sqlite/sqlite3.h"
Victor Costan538c14312019-03-25 19:13:17[diff] [blame]25
shess@chromium.org8d409412013-07-19 18:25:30[diff] [blame]26namespace sql{
27
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]28namespace{
29
30constexprchar kMainDatabaseName[]="main";
31
32}// namespace
33
34// static
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]35boolRecovery::ShouldAttemptRecovery(Database* database,int extended_error){
Evan Stadeb3be6372024-02-27 21:11:09[diff] [blame]36return database&& database->is_open()&&
Austin Sullivan18fdb6c92023-05-18 20:55:49[diff] [blame]37!database->DbPath(InternalApiToken()).empty()&&
Evan Stade2f6256b70d2024-01-26 14:58:55[diff] [blame]38#if BUILDFLAG(IS_FUCHSIA)
39// Recovering WAL databases is not supported on Fuchsia.
40!database->UseWALMode()&&
41#endif// BUILDFLAG(IS_FUCHSIA)
Austin Sullivan18fdb6c92023-05-18 20:55:49[diff] [blame]42IsErrorCatastrophic(extended_error);
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]43}
44
45// static
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]46SqliteResultCodeRecovery::RecoverDatabase(Database* database,
47Strategy strategy){
48auto recovery=Recovery(database, strategy);
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]49return recovery.RecoverAndReplaceDatabase();
50}
51
Austin Sullivan265c201b2023-05-31 02:04:26[diff] [blame]52// static
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]53boolRecovery::RecoverIfPossible(Database* database,
54int extended_error,
55Strategy strategy){
Evan Stadeb3be6372024-02-27 21:11:09[diff] [blame]56if(!ShouldAttemptRecovery(database, extended_error)){
Austin Sullivan265c201b2023-05-31 02:04:26[diff] [blame]57returnfalse;
58}
59
60// Recovery should be attempted. Since recovery must only be attempted from
61// within a database error callback, reset the error callback to prevent
62// re-entry.
63 database->reset_error_callback();
64
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]65auto result=Recovery::RecoverDatabase(database, strategy);
Evan Stadeb3be6372024-02-27 21:11:09[diff] [blame]66if(!IsSqliteSuccessCode(result)){
67 DLOG(ERROR)<<"Database recovery failed with result code: "<< result;
Austin Sullivan265c201b2023-05-31 02:04:26[diff] [blame]68}
69
70returntrue;
71}
72
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]73Recovery::Recovery(Database* database,Strategy strategy)
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]74: strategy_(strategy),
75 db_(database),
Anthony Vallée-Duboise3c94912024-12-12 16:47:47[diff] [blame]76 recover_db_(
Anthony Vallée-Dubois9adf0bb2025-02-03 17:01:41[diff] [blame]77DatabaseOptions().set_page_size(database? database->page_size():0),
Anthony Vallée-Duboise3c94912024-12-12 16:47:47[diff] [blame]78"Recovery"){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]79 CHECK(db_);
80 CHECK(db_->is_open());
Austin Sullivan2fbe496e2023-05-18 19:48:52[diff] [blame]81// Recovery is likely to be used in error handling. To prevent re-entry due to
82// errors while attempting to recover the database, the error callback must
83// not be set.
84 CHECK(!db_->has_error_callback());
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]85
86auto db_path= db_->DbPath(InternalApiToken());
87
88// Corruption recovery for in-memory databases is not supported.
89 CHECK(!db_path.empty());
90
Austin Sullivandb911aa2023-12-21 13:44:29[diff] [blame]91// Cache the database's histogram tag while the database is open.
92 database_uma_name_= db_->histogram_tag();
93
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]94 recovery_database_path_= db_path.AddExtensionASCII(".recovery");
95
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]96// Break any outstanding transactions on the original database, since the
97// recovery module opens a transaction on the database while recovery is in
98// progress.
99 db_->RollbackAllTransactions();
100}
101
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]102Recovery::~Recovery(){
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]103// Recovery result must be set before we reach this point.
104 CHECK_NE(result_,Result::kUnknown);
105
106 base::UmaHistogramEnumeration("Sql.Recovery.Result", result_);
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]107UmaHistogramSqliteResult("Sql.Recovery.ResultCode",
108static_cast<int>(sqlite_result_code_));
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]109
Austin Sullivan7b543402023-10-07 00:55:09[diff] [blame]110if(!database_uma_name_.empty()){
111 base::UmaHistogramEnumeration(
112 base::StrCat({"Sql.Recovery.Result.", database_uma_name_}), result_);
113UmaHistogramSqliteResult(
114 base::StrCat({"Sql.Recovery.ResultCode.", database_uma_name_}),
115static_cast<int>(sqlite_result_code_));
116}
117
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]118if(db_){
119if(result_==Result::kSuccess){
120// Poison the original handle, but don't raze the database.
121 db_->Poison();
122}else{
123 db_->RazeAndPoison();
124}
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]125}
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]126
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]127 db_=nullptr;
128
129if(recover_db_.is_open()){
130 recover_db_.Close();
131}
Alison Gale71bd8f152024-04-26 22:38:20[diff] [blame]132// TODO(crbug.com/40061775): Don't always delete the recovery db if we
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]133// are ever to keep around successfully-recovered, but unsuccessfully-restored
134// databases.
135 sql::Database::Delete(recovery_database_path_);
136}
137
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]138voidRecovery::SetRecoverySucceeded(){
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]139// Recovery result must only be set once.
140 CHECK_EQ(result_,Result::kUnknown);
141
142 result_=Result::kSuccess;
143}
144
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]145voidRecovery::SetRecoveryFailed(Result failure_result,
146SqliteResultCode result_code){
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]147// Recovery result must only be set once.
148 CHECK_EQ(result_,Result::kUnknown);
149
150switch(failure_result){
151caseResult::kUnknown:
152caseResult::kSuccess:
Peter Boström89c827082024-09-20 10:54:38[diff] [blame]153 NOTREACHED();
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]154caseResult::kFailedRecoveryInit:
155caseResult::kFailedRecoveryRun:
156caseResult::kFailedToOpenRecoveredDatabase:
157caseResult::kFailedMetaTableDoesNotExist:
158caseResult::kFailedMetaTableInit:
159caseResult::kFailedMetaTableVersionWasInvalid:
160caseResult::kFailedBackupInit:
161caseResult::kFailedBackupRun:
162break;
163}
164
165 result_= failure_result;
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]166 sqlite_result_code_= result_code;
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]167}
168
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]169SqliteResultCodeRecovery::RecoverAndReplaceDatabase(){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]170auto sqlite_result_code=AttemptToRecoverDatabaseToBackup();
171if(sqlite_result_code!=SqliteResultCode::kOk){
172return sqlite_result_code;
173}
174
175// Open a connection to the newly-created recovery database.
176if(!recover_db_.Open(recovery_database_path_)){
177 DVLOG(1)<<"Unable to open recovery database.";
178
Alison Gale71bd8f152024-04-26 22:38:20[diff] [blame]179// TODO(crbug.com/40061775): It's unfortunate to give up now, after
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]180// we've successfully recovered the database to a backup. Consider falling
181// back to base::Move().
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]182SetRecoveryFailed(Result::kFailedToOpenRecoveredDatabase,
183ToSqliteResultCode(recover_db_.GetErrorCode()));
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]184returnSqliteResultCode::kError;
185}
186
187if(strategy_==Strategy::kRecoverWithMetaVersionOrRaze&&
188!RecoveredDbHasValidMetaTable()){
189 DVLOG(1)<<"Could not read valid version number from recovery database.";
190returnSqliteResultCode::kError;
191}
192
193returnReplaceOriginalWithRecoveredDb();
194}
195
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]196SqliteResultCodeRecovery::AttemptToRecoverDatabaseToBackup(){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]197 CHECK(db_->is_open());
198 CHECK(!recover_db_.is_open());
199
200// See full documentation for the corruption recovery module in
201// https://sqlite.org/src/file/ext/recover/sqlite3recover.h
202
203// sqlite3_recover_init() create a new sqlite3_recover handle, with data being
204// recovered into a new database. This should very rarely fail - e.g. if
205// memory for the recovery object itself could not be allocated. If it does
206// fail, `recover` will be nullptr and an error code will surface when
207// attempting to configure the recovery object below.
208 sqlite3_recover* recover=
209 sqlite3_recover_init(db_->db(InternalApiToken()), kMainDatabaseName,
210 recovery_database_path_.AsUTF8Unsafe().c_str());
211
212// sqlite3_recover_config() configures the sqlite3_recover object.
213//
214// These functions should only fail if the above initialization failed, or if
215// invalid parameters are passed.
216
217// Don't bother creating a lost-and-found table.
218 sqlite3_recover_config(recover, SQLITE_RECOVER_LOST_AND_FOUND,nullptr);
219// Do not attempt to recover records from pages that appear to be linked to
220// the freelist, to avoid "recovering" deleted records.
221int kRecoverFreelist=0;
222 sqlite3_recover_config(recover, SQLITE_RECOVER_FREELIST_CORRUPT,
223static_cast<void*>(&kRecoverFreelist));
224// Attempt to recover ROWID values that are not INTEGER PRIMARY KEY.
225int kRecoverRowIds=1;
226 sqlite3_recover_config(recover, SQLITE_RECOVER_ROWIDS,
227static_cast<void*>(&kRecoverRowIds));
228
229auto sqlite_result_code=
230ToSqliteResultCode(sqlite3_recover_errcode(recover));
231if(sqlite_result_code!=SqliteResultCode::kOk){
232 CHECK_NE(sqlite_result_code,SqliteResultCode::kApiMisuse);
233
234// The recovery could not be configured.
Alison Gale71bd8f152024-04-26 22:38:20[diff] [blame]235// TODO(crbug.com/40061775): This is likely a transient issue, so we
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]236// could consider keeping the database intact in case the caller wants to
237// try again later. For now, we'll always raze.
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]238SetRecoveryFailed(Result::kFailedRecoveryInit, sqlite_result_code);
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]239
240 DVLOG(1)<<"recovery config error: "<< sqlite_result_code
241<< sqlite3_recover_errcode(recover);
242
243// Clean up the recovery object.
244 sqlite3_recover_finish(recover);
245return sqlite_result_code;
246}
247
248// sqlite3_recover_run() attempts to construct an copy of the database with
249// data corruption handled. It returns SQLITE_OK if recovery was successful.
250 sqlite_result_code=ToSqliteResultCode(sqlite3_recover_run(recover));
251
252// sqlite3_recover_finish() cleans up the recovery object. It should return
253// the same error code as from sqlite3_recover_run().
254auto finish_result_code=ToSqliteResultCode(sqlite3_recover_finish(recover));
255 CHECK_EQ(finish_result_code, sqlite_result_code);
256
257if(sqlite_result_code!=SqliteResultCode::kOk){
258// Could not recover the database.
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]259SetRecoveryFailed(Result::kFailedRecoveryRun, sqlite_result_code);
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]260
261 DVLOG(1)<<"recovery error: "<< sqlite_result_code
262<< sqlite3_recover_errmsg(recover);
263}
264
265return sqlite_result_code;
266}
267
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]268SqliteResultCodeRecovery::ReplaceOriginalWithRecoveredDb(){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]269 CHECK(db_->is_open());
270 CHECK(recover_db_.is_open());
271
272// sqlite3_backup_init() fails if a transaction is ongoing. This should be
273// rare, since we rolled back all transactions in this object's constructor.
274 sqlite3_backup* backup= sqlite3_backup_init(
275 db_->db(InternalApiToken()), kMainDatabaseName,
276 recover_db_.db(InternalApiToken()), kMainDatabaseName);
277if(!backup){
278// Error code is in the destination database handle.
279 DVLOG(1)<<"sqlite3_backup_init() failed: "
280<< sqlite3_errmsg(db_->db(InternalApiToken()));
281
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]282auto result_code=
283ToSqliteResultCode(sqlite3_errcode(db_->db(InternalApiToken())));
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]284
Alison Gale71bd8f152024-04-26 22:38:20[diff] [blame]285// TODO(crbug.com/40061775): It's unfortunate to give up now, after
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]286// we've successfully recovered the database. Consider falling back to
287// base::Move().
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]288SetRecoveryFailed(Result::kFailedBackupInit, result_code);
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]289return result_code;
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]290}
291
292// sqlite3_backup_step() copies pages from the source to the destination
293// database. It returns SQLITE_DONE if copying successfully completed, or some
294// other error on failure.
Alison Gale71bd8f152024-04-26 22:38:20[diff] [blame]295// TODO(crbug.com/40061775): Some of these errors are transient and the
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]296// operation could feasibly succeed at a later time. Consider keeping around
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]297// successfully-recovered, but unsuccessfully-restored databases or falling
298// back to base::Move().
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]299constexprint kUnlimitedPageCount=-1;// Back up entire database.
300auto sqlite_result_code=
301ToSqliteResultCode(sqlite3_backup_step(backup, kUnlimitedPageCount));
302
303// sqlite3_backup_remaining() returns the number of pages still to be backed
304// up, which should be zero if sqlite3_backup_step() completed successfully.
305int pages_remaining= sqlite3_backup_remaining(backup);
306
307// sqlite3_backup_finish() releases the sqlite3_backup object.
308//
309// It returns an error code only if the backup encountered a permanent error.
310// We use the the sqlite3_backup_step() result instead, because it also tells
311// us about temporary errors, like SQLITE_BUSY.
312//
313// We pass the sqlite3_backup_finish() result code through
314// ToSqliteResultCode() to catch codes that should never occur, like
315// SQLITE_MISUSE.
316 std::ignore=ToSqliteResultCode(sqlite3_backup_finish(backup));
317
318if(sqlite_result_code!=SqliteResultCode::kDone){
319 CHECK_NE(sqlite_result_code,SqliteResultCode::kOk)
320<<"sqlite3_backup_step() returned SQLITE_OK (instead of SQLITE_DONE) "
321<<"when asked to back up the entire database";
322
323 DVLOG(1)<<"sqlite3_backup_step() failed: "
324<< sqlite3_errmsg(db_->db(InternalApiToken()));
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]325SetRecoveryFailed(Result::kFailedBackupRun, sqlite_result_code);
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]326return sqlite_result_code;
327}
328
329// The original database was successfully recovered and replaced. Hooray!
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]330SetRecoverySucceeded();
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]331
332 CHECK_EQ(pages_remaining,0);
333returnSqliteResultCode::kOk;
334}
335
Evan Stade4c7d6b32024-03-12 16:50:27[diff] [blame]336boolRecovery::RecoveredDbHasValidMetaTable(){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]337 CHECK(recover_db_.is_open());
338
339if(!MetaTable::DoesTableExist(&recover_db_)){
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]340 DVLOG(1)<<"Meta table does not exist in recovery database.";
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]341SetRecoveryFailed(Result::kFailedMetaTableDoesNotExist,
342ToSqliteResultCode(recover_db_.GetErrorCode()));
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]343returnfalse;
344}
345
346// MetaTable::Init will not create a meta table if one already exists.
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]347 sql::MetaTable meta_table;
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]348if(!meta_table.Init(&recover_db_,/*version=*/1,
349/*compatible_version=*/1)){
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]350SetRecoveryFailed(Result::kFailedMetaTableInit,
351ToSqliteResultCode(recover_db_.GetErrorCode()));
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]352returnfalse;
353}
354
355// Confirm that we can read a valid version number from the recovered table.
356if(meta_table.GetVersionNumber()<=0){
Austin Sullivan54930842023-06-06 21:09:01[diff] [blame]357SetRecoveryFailed(Result::kFailedMetaTableVersionWasInvalid,
358ToSqliteResultCode(recover_db_.GetErrorCode()));
Austin Sullivanc99e9302023-05-19 02:41:45[diff] [blame]359returnfalse;
360}
361
362returntrue;
Austin Sullivan0593ef92023-05-17 20:46:30[diff] [blame]363}
364
shess@chromium.org8d409412013-07-19 18:25:30[diff] [blame]365}// namespace sql

[8]ページ先頭

©2009-2025 Movatter.jp