1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
sql / recovery.h [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SQL_RECOVERY_H_
#define SQL_RECOVERY_H_
#include <stddef.h>
#include <string>
#include "base/component_export.h"
#include "base/memory/raw_ptr.h"
#include "sql/database.h"
#include "sql/internal_api_token.h"
#include "sql/sqlite_result_code_values.h"
namespace base {
class FilePath;
}
namespace sql {
// Recovery module for sql/. Please see the `RecoverIfPossible()` method for how
// to use this class.
//
// This module is capable of recovering databases which the legacy recovery
// module could not recover. These include:
// - tables with the WITHOUT ROWID optimization
// - databases which use Write-Ahead Log (i.e. WAL mode)
// - NOTE: as WAL mode is still experimental (see https://crbug.com/1416213)
// recovery should not be attempted on WAL databases for now.
//
// Uses SQLite's recovery extension: https://www.sqlite.org/recovery.html
class COMPONENT_EXPORT(SQL) Recovery {
public:
enum class Strategy {
// Razes the database if it could not be recovered.
kRecoverOrRaze,
// Razes the database if it could not be recovered, or if a valid meta table
// with a version value could not be determined from the recovered database.
// Use this strategy if your client makes assertions about the version of
// the database schema.
kRecoverWithMetaVersionOrRaze,
// TODO(crbug.com/40061775): Consider exposing a way to keep around a
// successfully-recovered, but unsuccessfully-restored database if needed.
};
// These values are persisted to logs. Entries should not be renumbered
// and numeric values should never be reused.
enum class Result {
// Outcome not yet known. This value should never be logged.
kUnknown = 0,
// Successfully completed the full database recovery process.
kSuccess = 1,
// Failed to initialize and configure the sqlite3_recover object.
kFailedRecoveryInit = 2,
// Failed to run recovery with the sqlite3_recover object.
kFailedRecoveryRun = 3,
// The database was successfully recovered to a backup, but we could not
// open the newly-recovered database in order to copy it to the original
// database.
kFailedToOpenRecoveredDatabase = 4,
// The database was successfully recovered to a backup, but a meta table
// could not be found in the recovered database.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableDoesNotExist = 5,
// The database was successfully recovered to a backup, but the meta table
// could not be initialized.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableInit = 6,
// The database was successfully recovered to a backup, but a valid
// (meaning, positive) version number could not be read from the meta table.
// Only valid when using Strategy::kRecoverWithMetaVersionOrRaze.
kFailedMetaTableVersionWasInvalid = 7,
// Failed to initialize and configure the sqlite3_backup object.
kFailedBackupInit = 8,
// Failed to run backup with the sqlite3_backup object.
kFailedBackupRun = 9,
kMaxValue = kFailedBackupRun,
};
// Returns true if `RecoverDatabase()` can plausibly fix `database` given this
// `extended_error`. This does not guarantee that `RecoverDatabase()` will
// successfully recover the database.
//
// Note that even if this method returns true, the database's error callback
// must be reset before recovery can be attempted.
[[nodiscard]] static bool ShouldAttemptRecovery(Database* database,
int extended_error);
// Use `RecoverIfPossible()` below rather than using this method directly.
//
// Attempts to recover `database`, and razes the database if it could not be
// recovered according to `strategy`. After attempting recovery, the database
// can be re-opened and assumed to be free of corruption.
//
// Use Database::set_histogram_tag() to log UMA for recovery results specific
// to the given feature database.
//
// It is not considered an error if some or all of the data cannot be
// recovered due to database corruption, so it is possible that some records
// could not be salvaged from the corrupted database.
// TODO(crbug.com/40061775): Support the lost-and-found table if the
// need arises to try to restore all these records.
//
// It is illegal to attempt recovery if:
// - `database` is null,
// - `database` is not open,
// - `database` is an in-memory or temporary database, or
// - `database` has an error callback set
//
// During the recovery process, `database` is poisoned so that operations on
// the stack do not accidentally disrupt the restored data.
//
// Returns a SQLite error code specifying whether the database was
// successfully recovered.
[[nodiscard]] static SqliteResultCode RecoverDatabase(Database* database,
Strategy strategy);
// Similar to `RecoverDatabase()` above, but with a couple key differences:
// - Can be called without first checking `ShouldAttemptRecovery()`.
// - `database`'s error callback will be reset if recovery is attempted.
// - Must only be called from within a database error callback.
//
// Recommended usage from within a database error callback:
//
// // Attempt to recover the database, if recovery is possible.
// if (sql::Recovery::RecoverIfPossible(
// &db, extended_error,
// sql::Recovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
// // Recovery was attempted. The database handle has been poisoned and the
// // error callback has been reset.
//
// // ...
// }
//
[[nodiscard]] static bool RecoverIfPossible(Database* database,
int extended_error,
Strategy strategy);
Recovery(const Recovery&) = delete;
Recovery& operator=(const Recovery&) = delete;
private:
Recovery(Database* database, Strategy strategy);
~Recovery();
// Entry point.
SqliteResultCode RecoverAndReplaceDatabase();
// Use SQLite's corruption recovery module to store the recovered content in
// `recover_db_`. See https://www.sqlite.org/recovery.html
SqliteResultCode AttemptToRecoverDatabaseToBackup();
bool RecoveredDbHasValidMetaTable();
// Use SQLite's Online Backup API to replace the original database with
// `recover_db_`. See https://www.sqlite.org/backup.html
SqliteResultCode ReplaceOriginalWithRecoveredDb();
void SetRecoverySucceeded();
void SetRecoveryFailed(Result failure_result, SqliteResultCode result_code);
const Strategy strategy_;
// If non-empty, UMA will be logged with the result of the recovery for this
// specific database.
std::string database_uma_name_;
// Result of the recovery. This value must be set to something other than
// `kUnknown` before this object is destroyed.
Result result_ = Result::kUnknown;
SqliteResultCode sqlite_result_code_ = SqliteResultCode::kOk;
raw_ptr<Database> db_; // Original Database connection.
Database recover_db_; // Recovery Database connection.
base::FilePath recovery_database_path_;
};
} // namespace sql
#endif // SQL_RECOVERY_H_