OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "webkit/fileapi/file_system_directory_database.h" | 5 #include "webkit/fileapi/file_system_directory_database.h" |
6 | 6 |
7 #include <math.h> | 7 #include <math.h> |
8 | 8 |
9 #include "base/location.h" | 9 #include "base/location.h" |
10 #include "base/pickle.h" | 10 #include "base/pickle.h" |
11 #include "base/string_number_conversions.h" | 11 #include "base/string_number_conversions.h" |
12 #include "base/string_util.h" | 12 #include "base/string_util.h" |
13 #include "base/sys_string_conversions.h" | 13 #include "base/sys_string_conversions.h" |
14 #include "third_party/leveldatabase/src/include/leveldb/iterator.h" | 14 #include "third_party/leveldatabase/src/include/leveldb/iterator.h" |
15 #include "third_party/leveldatabase/src/include/leveldb/status.h" | |
15 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 16 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
16 #include "webkit/fileapi/file_system_util.h" | 17 #include "webkit/fileapi/file_system_util.h" |
17 | 18 |
18 namespace { | 19 namespace { |
19 | 20 |
20 bool PickleFromFileInfo( | 21 bool PickleFromFileInfo( |
21 const fileapi::FileSystemDirectoryDatabase::FileInfo& info, | 22 const fileapi::FileSystemDirectoryDatabase::FileInfo& info, |
22 Pickle* pickle) { | 23 Pickle* pickle) { |
23 DCHECK(pickle); | 24 DCHECK(pickle); |
24 std::string data_path; | 25 std::string data_path; |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
123 #elif defined(OS_WIN) | 124 #elif defined(OS_WIN) |
124 path_ = base::SysWideToUTF8(path.value()); | 125 path_ = base::SysWideToUTF8(path.value()); |
125 #endif | 126 #endif |
126 } | 127 } |
127 | 128 |
128 FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() { | 129 FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() { |
129 } | 130 } |
130 | 131 |
131 bool FileSystemDirectoryDatabase::GetChildWithName( | 132 bool FileSystemDirectoryDatabase::GetChildWithName( |
132 FileId parent_id, const FilePath::StringType& name, FileId* child_id) { | 133 FileId parent_id, const FilePath::StringType& name, FileId* child_id) { |
133 if (!Init()) | 134 if (!Init(true)) |
jsbell
2012/03/09 23:10:11
The Boolean argument isn't very readable. Use an e
tzik
2012/03/10 00:16:55
Done.
| |
134 return false; | 135 return false; |
135 DCHECK(child_id); | 136 DCHECK(child_id); |
136 std::string child_key = GetChildLookupKey(parent_id, name); | 137 std::string child_key = GetChildLookupKey(parent_id, name); |
137 std::string child_id_string; | 138 std::string child_id_string; |
138 leveldb::Status status = | 139 leveldb::Status status = |
139 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | 140 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); |
140 if (status.IsNotFound()) | 141 if (status.IsNotFound()) |
141 return false; | 142 return false; |
142 if (status.ok()) { | 143 if (status.ok()) { |
143 if (!base::StringToInt64(child_id_string, child_id)) { | 144 if (!base::StringToInt64(child_id_string, child_id)) { |
(...skipping 20 matching lines...) Expand all Loading... | |
164 if (!GetChildWithName(local_id, name, &local_id)) | 165 if (!GetChildWithName(local_id, name, &local_id)) |
165 return false; | 166 return false; |
166 } | 167 } |
167 *file_id = local_id; | 168 *file_id = local_id; |
168 return true; | 169 return true; |
169 } | 170 } |
170 | 171 |
171 bool FileSystemDirectoryDatabase::ListChildren( | 172 bool FileSystemDirectoryDatabase::ListChildren( |
172 FileId parent_id, std::vector<FileId>* children) { | 173 FileId parent_id, std::vector<FileId>* children) { |
173 // Check to add later: fail if parent is a file, at least in debug builds. | 174 // Check to add later: fail if parent is a file, at least in debug builds. |
174 if (!Init()) | 175 if (!Init(true)) |
175 return false; | 176 return false; |
176 DCHECK(children); | 177 DCHECK(children); |
177 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); | 178 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); |
178 | 179 |
179 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | 180 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); |
180 iter->Seek(child_key_prefix); | 181 iter->Seek(child_key_prefix); |
181 children->clear(); | 182 children->clear(); |
182 while (iter->Valid() && | 183 while (iter->Valid() && |
183 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) { | 184 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) { |
184 std::string child_id_string = iter->value().ToString(); | 185 std::string child_id_string = iter->value().ToString(); |
185 FileId child_id; | 186 FileId child_id; |
186 if (!base::StringToInt64(child_id_string, &child_id)) { | 187 if (!base::StringToInt64(child_id_string, &child_id)) { |
187 LOG(ERROR) << "Hit database corruption!"; | 188 LOG(ERROR) << "Hit database corruption!"; |
188 return false; | 189 return false; |
189 } | 190 } |
190 children->push_back(child_id); | 191 children->push_back(child_id); |
191 iter->Next(); | 192 iter->Next(); |
192 } | 193 } |
193 return true; | 194 return true; |
194 } | 195 } |
195 | 196 |
196 bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { | 197 bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { |
197 if (!Init()) | 198 if (!Init(true)) |
198 return false; | 199 return false; |
199 DCHECK(info); | 200 DCHECK(info); |
200 std::string file_key = GetFileLookupKey(file_id); | 201 std::string file_key = GetFileLookupKey(file_id); |
201 std::string file_data_string; | 202 std::string file_data_string; |
202 leveldb::Status status = | 203 leveldb::Status status = |
203 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); | 204 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); |
204 if (status.ok()) { | 205 if (status.ok()) { |
205 return FileInfoFromPickle( | 206 return FileInfoFromPickle( |
206 Pickle(file_data_string.data(), file_data_string.length()), info); | 207 Pickle(file_data_string.data(), file_data_string.length()), info); |
207 } | 208 } |
208 // Special-case the root, for databases that haven't been initialized yet. | 209 // Special-case the root, for databases that haven't been initialized yet. |
209 // Without this, a query for the root's file info, made before creating the | 210 // Without this, a query for the root's file info, made before creating the |
210 // first file in the database, will fail and confuse callers. | 211 // first file in the database, will fail and confuse callers. |
211 if (status.IsNotFound() && !file_id) { | 212 if (status.IsNotFound() && !file_id) { |
212 info->name = FilePath::StringType(); | 213 info->name = FilePath::StringType(); |
213 info->data_path = FilePath(); | 214 info->data_path = FilePath(); |
214 info->modification_time = base::Time::Now(); | 215 info->modification_time = base::Time::Now(); |
215 info->parent_id = 0; | 216 info->parent_id = 0; |
216 return true; | 217 return true; |
217 } | 218 } |
218 HandleError(FROM_HERE, status); | 219 HandleError(FROM_HERE, status); |
219 return false; | 220 return false; |
220 } | 221 } |
221 | 222 |
222 bool FileSystemDirectoryDatabase::AddFileInfo( | 223 bool FileSystemDirectoryDatabase::AddFileInfo( |
223 const FileInfo& info, FileId* file_id) { | 224 const FileInfo& info, FileId* file_id) { |
224 if (!Init()) | 225 if (!Init(true)) |
225 return false; | 226 return false; |
226 DCHECK(file_id); | 227 DCHECK(file_id); |
227 std::string child_key = GetChildLookupKey(info.parent_id, info.name); | 228 std::string child_key = GetChildLookupKey(info.parent_id, info.name); |
228 std::string child_id_string; | 229 std::string child_id_string; |
229 leveldb::Status status = | 230 leveldb::Status status = |
230 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | 231 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); |
231 if (status.ok()) { | 232 if (status.ok()) { |
232 LOG(ERROR) << "File exists already!"; | 233 LOG(ERROR) << "File exists already!"; |
233 return false; | 234 return false; |
234 } | 235 } |
(...skipping 21 matching lines...) Expand all Loading... | |
256 status = db_->Write(leveldb::WriteOptions(), &batch); | 257 status = db_->Write(leveldb::WriteOptions(), &batch); |
257 if (!status.ok()) { | 258 if (!status.ok()) { |
258 HandleError(FROM_HERE, status); | 259 HandleError(FROM_HERE, status); |
259 return false; | 260 return false; |
260 } | 261 } |
261 *file_id = temp_id; | 262 *file_id = temp_id; |
262 return true; | 263 return true; |
263 } | 264 } |
264 | 265 |
265 bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id) { | 266 bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id) { |
266 if (!Init()) | 267 if (!Init(true)) |
267 return false; | 268 return false; |
268 leveldb::WriteBatch batch; | 269 leveldb::WriteBatch batch; |
269 if (!RemoveFileInfoHelper(file_id, &batch)) | 270 if (!RemoveFileInfoHelper(file_id, &batch)) |
270 return false; | 271 return false; |
271 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 272 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
272 if (!status.ok()) { | 273 if (!status.ok()) { |
273 HandleError(FROM_HERE, status); | 274 HandleError(FROM_HERE, status); |
274 return false; | 275 return false; |
275 } | 276 } |
276 return true; | 277 return true; |
277 } | 278 } |
278 | 279 |
279 bool FileSystemDirectoryDatabase::UpdateFileInfo( | 280 bool FileSystemDirectoryDatabase::UpdateFileInfo( |
280 FileId file_id, const FileInfo& new_info) { | 281 FileId file_id, const FileInfo& new_info) { |
281 // TODO: We should also check to see that this doesn't create a loop, but | 282 // TODO: We should also check to see that this doesn't create a loop, but |
282 // perhaps only in a debug build. | 283 // perhaps only in a debug build. |
283 if (!Init()) | 284 if (!Init(true)) |
284 return false; | 285 return false; |
285 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. | 286 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. |
286 FileInfo old_info; | 287 FileInfo old_info; |
287 if (!GetFileInfo(file_id, &old_info)) | 288 if (!GetFileInfo(file_id, &old_info)) |
288 return false; | 289 return false; |
289 if (old_info.parent_id != new_info.parent_id && | 290 if (old_info.parent_id != new_info.parent_id && |
290 !VerifyIsDirectory(new_info.parent_id)) | 291 !VerifyIsDirectory(new_info.parent_id)) |
291 return false; | 292 return false; |
292 if (old_info.parent_id != new_info.parent_id || | 293 if (old_info.parent_id != new_info.parent_id || |
293 old_info.name != new_info.name) { | 294 old_info.name != new_info.name) { |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
357 pickle.size())); | 358 pickle.size())); |
358 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 359 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
359 if (!status.ok()) { | 360 if (!status.ok()) { |
360 HandleError(FROM_HERE, status); | 361 HandleError(FROM_HERE, status); |
361 return false; | 362 return false; |
362 } | 363 } |
363 return true; | 364 return true; |
364 } | 365 } |
365 | 366 |
366 bool FileSystemDirectoryDatabase::GetNextInteger(int64* next) { | 367 bool FileSystemDirectoryDatabase::GetNextInteger(int64* next) { |
367 if (!Init()) | 368 if (!Init(true)) |
368 return false; | 369 return false; |
369 DCHECK(next); | 370 DCHECK(next); |
370 std::string int_string; | 371 std::string int_string; |
371 leveldb::Status status = | 372 leveldb::Status status = |
372 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); | 373 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); |
373 if (status.ok()) { | 374 if (status.ok()) { |
374 int64 temp; | 375 int64 temp; |
375 if (!base::StringToInt64(int_string, &temp)) { | 376 if (!base::StringToInt64(int_string, &temp)) { |
376 LOG(ERROR) << "Hit database corruption!"; | 377 LOG(ERROR) << "Hit database corruption!"; |
377 return false; | 378 return false; |
(...skipping 28 matching lines...) Expand all Loading... | |
406 name = base::SysWideToUTF8(path.value()); | 407 name = base::SysWideToUTF8(path.value()); |
407 #endif | 408 #endif |
408 leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options()); | 409 leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options()); |
409 if (status.ok()) | 410 if (status.ok()) |
410 return true; | 411 return true; |
411 LOG(WARNING) << "Failed to destroy a database with status " << | 412 LOG(WARNING) << "Failed to destroy a database with status " << |
412 status.ToString(); | 413 status.ToString(); |
413 return false; | 414 return false; |
414 } | 415 } |
415 | 416 |
416 bool FileSystemDirectoryDatabase::Init() { | 417 bool FileSystemDirectoryDatabase::Init(bool cleanup_if_corrupted) { |
417 if (db_.get()) | 418 if (db_.get()) |
418 return true; | 419 return true; |
419 | 420 |
420 leveldb::Options options; | 421 leveldb::Options options; |
421 options.create_if_missing = true; | 422 options.create_if_missing = true; |
422 leveldb::DB* db; | 423 leveldb::DB* db; |
423 leveldb::Status status = leveldb::DB::Open(options, path_, &db); | 424 leveldb::Status status = leveldb::DB::Open(options, path_, &db); |
424 if (status.ok()) { | 425 // TODO(tzik): Collect status metrics here. |
425 db_.reset(db); | 426 if (status.ok()) { |
426 return true; | 427 db_.reset(db); |
427 } | 428 return true; |
428 HandleError(FROM_HERE, status); | 429 } |
429 return false; | 430 HandleError(FROM_HERE, status); |
431 | |
432 if (cleanup_if_corrupted && CheckIfDatabaseCorrupted(status)) { | |
433 LOG(WARNING) << "FileSystem API directory database is corrupted." | |
434 << " Attempting cleanup."; | |
435 if (leveldb::DestroyDB(path_, leveldb::Options()).ok()) { | |
436 LOG(WARNING) << "FileSystem API directory database cleanup completed." | |
437 << " Reopening."; | |
438 return Init(false); | |
439 } | |
440 LOG(WARNING) << "Failed to cleanup FileSystem API directory database."; | |
441 } | |
442 | |
443 return false; | |
444 } | |
445 | |
446 bool FileSystemDirectoryDatabase::CheckIfDatabaseCorrupted( | |
jsbell
2012/03/09 23:10:11
What is the benefit in factoring this method out?
tzik
2012/03/10 00:16:55
I'd initially thought, we have many thing to do he
| |
447 const leveldb::Status& status) const { | |
448 // TODO(tzik): Return status.code() == leveldb::Status::kCorruption, after | |
449 // leveldb roll. | |
450 return !status.ok(); | |
430 } | 451 } |
431 | 452 |
432 bool FileSystemDirectoryDatabase::StoreDefaultValues() { | 453 bool FileSystemDirectoryDatabase::StoreDefaultValues() { |
433 // Verify that this is a totally new database, and initialize it. | 454 // Verify that this is a totally new database, and initialize it. |
434 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | 455 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); |
435 iter->SeekToFirst(); | 456 iter->SeekToFirst(); |
436 if (iter->Valid()) { // DB was not empty--we shouldn't have been called. | 457 if (iter->Valid()) { // DB was not empty--we shouldn't have been called. |
437 LOG(ERROR) << "File system origin database is corrupt!"; | 458 LOG(ERROR) << "File system origin database is corrupt!"; |
438 return false; | 459 return false; |
439 } | 460 } |
440 // This is always the first write into the database. If we ever add a | 461 // This is always the first write into the database. If we ever add a |
441 // version number, it should go in this transaction too. | 462 // version number, it should go in this transaction too. |
442 FileInfo root; | 463 FileInfo root; |
443 root.parent_id = 0; | 464 root.parent_id = 0; |
444 root.modification_time = base::Time::Now(); | 465 root.modification_time = base::Time::Now(); |
445 leveldb::WriteBatch batch; | 466 leveldb::WriteBatch batch; |
446 if (!AddFileInfoHelper(root, 0, &batch)) | 467 if (!AddFileInfoHelper(root, 0, &batch)) |
447 return false; | 468 return false; |
448 batch.Put(LastFileIdKey(), base::Int64ToString(0)); | 469 batch.Put(LastFileIdKey(), base::Int64ToString(0)); |
449 batch.Put(LastIntegerKey(), base::Int64ToString(-1)); | 470 batch.Put(LastIntegerKey(), base::Int64ToString(-1)); |
450 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | 471 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
451 if (!status.ok()) { | 472 if (!status.ok()) { |
452 HandleError(FROM_HERE, status); | 473 HandleError(FROM_HERE, status); |
453 return false; | 474 return false; |
454 } | 475 } |
455 return true; | 476 return true; |
456 } | 477 } |
457 | 478 |
458 bool FileSystemDirectoryDatabase::GetLastFileId(FileId* file_id) { | 479 bool FileSystemDirectoryDatabase::GetLastFileId(FileId* file_id) { |
459 if (!Init()) | 480 if (!Init(true)) |
460 return false; | 481 return false; |
461 DCHECK(file_id); | 482 DCHECK(file_id); |
462 std::string id_string; | 483 std::string id_string; |
463 leveldb::Status status = | 484 leveldb::Status status = |
464 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); | 485 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); |
465 if (status.ok()) { | 486 if (status.ok()) { |
466 if (!base::StringToInt64(id_string, file_id)) { | 487 if (!base::StringToInt64(id_string, file_id)) { |
467 LOG(ERROR) << "Hit database corruption!"; | 488 LOG(ERROR) << "Hit database corruption!"; |
468 return false; | 489 return false; |
469 } | 490 } |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
539 | 560 |
540 void FileSystemDirectoryDatabase::HandleError( | 561 void FileSystemDirectoryDatabase::HandleError( |
541 const tracked_objects::Location& from_here, | 562 const tracked_objects::Location& from_here, |
542 leveldb::Status status) { | 563 leveldb::Status status) { |
543 LOG(ERROR) << "FileSystemDirectoryDatabase failed at: " | 564 LOG(ERROR) << "FileSystemDirectoryDatabase failed at: " |
544 << from_here.ToString() << " with error: " << status.ToString(); | 565 << from_here.ToString() << " with error: " << status.ToString(); |
545 db_.reset(); | 566 db_.reset(); |
546 } | 567 } |
547 | 568 |
548 } // namespace fileapi | 569 } // namespace fileapi |
OLD | NEW |