OLD | NEW |
| (Empty) |
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 "webkit/browser/fileapi/sandbox_directory_database.h" | |
6 | |
7 #include <math.h> | |
8 #include <algorithm> | |
9 #include <set> | |
10 #include <stack> | |
11 | |
12 #include "base/file_util.h" | |
13 #include "base/files/file_enumerator.h" | |
14 #include "base/location.h" | |
15 #include "base/metrics/histogram.h" | |
16 #include "base/pickle.h" | |
17 #include "base/strings/string_number_conversions.h" | |
18 #include "base/strings/string_util.h" | |
19 #include "third_party/leveldatabase/src/include/leveldb/db.h" | |
20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | |
21 #include "webkit/browser/fileapi/file_system_usage_cache.h" | |
22 #include "webkit/common/fileapi/file_system_util.h" | |
23 | |
24 namespace { | |
25 | |
26 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info, | |
27 Pickle* pickle) { | |
28 DCHECK(pickle); | |
29 std::string data_path; | |
30 // Round off here to match the behavior of the filesystem on real files. | |
31 base::Time time = | |
32 base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT())); | |
33 std::string name; | |
34 | |
35 data_path = storage::FilePathToString(info.data_path); | |
36 name = storage::FilePathToString(base::FilePath(info.name)); | |
37 | |
38 if (pickle->WriteInt64(info.parent_id) && | |
39 pickle->WriteString(data_path) && | |
40 pickle->WriteString(name) && | |
41 pickle->WriteInt64(time.ToInternalValue())) | |
42 return true; | |
43 | |
44 NOTREACHED(); | |
45 return false; | |
46 } | |
47 | |
48 bool FileInfoFromPickle(const Pickle& pickle, | |
49 storage::SandboxDirectoryDatabase::FileInfo* info) { | |
50 PickleIterator iter(pickle); | |
51 std::string data_path; | |
52 std::string name; | |
53 int64 internal_time; | |
54 | |
55 if (pickle.ReadInt64(&iter, &info->parent_id) && | |
56 pickle.ReadString(&iter, &data_path) && | |
57 pickle.ReadString(&iter, &name) && | |
58 pickle.ReadInt64(&iter, &internal_time)) { | |
59 info->data_path = storage::StringToFilePath(data_path); | |
60 info->name = storage::StringToFilePath(name).value(); | |
61 info->modification_time = base::Time::FromInternalValue(internal_time); | |
62 return true; | |
63 } | |
64 LOG(ERROR) << "Pickle could not be digested!"; | |
65 return false; | |
66 } | |
67 | |
68 const base::FilePath::CharType kDirectoryDatabaseName[] = | |
69 FILE_PATH_LITERAL("Paths"); | |
70 const char kChildLookupPrefix[] = "CHILD_OF:"; | |
71 const char kChildLookupSeparator[] = ":"; | |
72 const char kLastFileIdKey[] = "LAST_FILE_ID"; | |
73 const char kLastIntegerKey[] = "LAST_INTEGER"; | |
74 const int64 kMinimumReportIntervalHours = 1; | |
75 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit"; | |
76 const char kDatabaseRepairHistogramLabel[] = | |
77 "FileSystem.DirectoryDatabaseRepair"; | |
78 | |
79 enum InitStatus { | |
80 INIT_STATUS_OK = 0, | |
81 INIT_STATUS_CORRUPTION, | |
82 INIT_STATUS_IO_ERROR, | |
83 INIT_STATUS_UNKNOWN_ERROR, | |
84 INIT_STATUS_MAX | |
85 }; | |
86 | |
87 enum RepairResult { | |
88 DB_REPAIR_SUCCEEDED = 0, | |
89 DB_REPAIR_FAILED, | |
90 DB_REPAIR_MAX | |
91 }; | |
92 | |
93 std::string GetChildLookupKey( | |
94 storage::SandboxDirectoryDatabase::FileId parent_id, | |
95 const base::FilePath::StringType& child_name) { | |
96 std::string name; | |
97 name = storage::FilePathToString(base::FilePath(child_name)); | |
98 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + | |
99 std::string(kChildLookupSeparator) + name; | |
100 } | |
101 | |
102 std::string GetChildListingKeyPrefix( | |
103 storage::SandboxDirectoryDatabase::FileId parent_id) { | |
104 return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + | |
105 std::string(kChildLookupSeparator); | |
106 } | |
107 | |
108 const char* LastFileIdKey() { | |
109 return kLastFileIdKey; | |
110 } | |
111 | |
112 const char* LastIntegerKey() { | |
113 return kLastIntegerKey; | |
114 } | |
115 | |
116 std::string GetFileLookupKey( | |
117 storage::SandboxDirectoryDatabase::FileId file_id) { | |
118 return base::Int64ToString(file_id); | |
119 } | |
120 | |
121 // Assumptions: | |
122 // - Any database entry is one of: | |
123 // - ("CHILD_OF:|parent_id|:<name>", "|file_id|"), | |
124 // - ("LAST_FILE_ID", "|last_file_id|"), | |
125 // - ("LAST_INTEGER", "|last_integer|"), | |
126 // - ("|file_id|", "pickled FileInfo") | |
127 // where FileInfo has |parent_id|, |data_path|, |name| and | |
128 // |modification_time|, | |
129 // Constraints: | |
130 // - Each file in the database has unique backing file. | |
131 // - Each file in |filesystem_data_directory_| has a database entry. | |
132 // - Directory structure is tree, i.e. connected and acyclic. | |
133 class DatabaseCheckHelper { | |
134 public: | |
135 typedef storage::SandboxDirectoryDatabase::FileId FileId; | |
136 typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo; | |
137 | |
138 DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db, | |
139 leveldb::DB* db, | |
140 const base::FilePath& path); | |
141 | |
142 bool IsFileSystemConsistent() { | |
143 return IsDatabaseEmpty() || | |
144 (ScanDatabase() && ScanDirectory() && ScanHierarchy()); | |
145 } | |
146 | |
147 private: | |
148 bool IsDatabaseEmpty(); | |
149 // These 3 methods need to be called in the order. Each method requires its | |
150 // previous method finished successfully. They also require the database is | |
151 // not empty. | |
152 bool ScanDatabase(); | |
153 bool ScanDirectory(); | |
154 bool ScanHierarchy(); | |
155 | |
156 storage::SandboxDirectoryDatabase* dir_db_; | |
157 leveldb::DB* db_; | |
158 base::FilePath path_; | |
159 | |
160 std::set<base::FilePath> files_in_db_; | |
161 | |
162 size_t num_directories_in_db_; | |
163 size_t num_files_in_db_; | |
164 size_t num_hierarchy_links_in_db_; | |
165 | |
166 FileId last_file_id_; | |
167 FileId last_integer_; | |
168 }; | |
169 | |
170 DatabaseCheckHelper::DatabaseCheckHelper( | |
171 storage::SandboxDirectoryDatabase* dir_db, | |
172 leveldb::DB* db, | |
173 const base::FilePath& path) | |
174 : dir_db_(dir_db), | |
175 db_(db), | |
176 path_(path), | |
177 num_directories_in_db_(0), | |
178 num_files_in_db_(0), | |
179 num_hierarchy_links_in_db_(0), | |
180 last_file_id_(-1), | |
181 last_integer_(-1) { | |
182 DCHECK(dir_db_); | |
183 DCHECK(db_); | |
184 DCHECK(!path_.empty() && base::DirectoryExists(path_)); | |
185 } | |
186 | |
187 bool DatabaseCheckHelper::IsDatabaseEmpty() { | |
188 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
189 itr->SeekToFirst(); | |
190 return !itr->Valid(); | |
191 } | |
192 | |
193 bool DatabaseCheckHelper::ScanDatabase() { | |
194 // Scans all database entries sequentially to verify each of them has unique | |
195 // backing file. | |
196 int64 max_file_id = -1; | |
197 std::set<FileId> file_ids; | |
198 | |
199 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
200 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { | |
201 std::string key = itr->key().ToString(); | |
202 if (StartsWithASCII(key, kChildLookupPrefix, true)) { | |
203 // key: "CHILD_OF:<parent_id>:<name>" | |
204 // value: "<child_id>" | |
205 ++num_hierarchy_links_in_db_; | |
206 } else if (key == kLastFileIdKey) { | |
207 // key: "LAST_FILE_ID" | |
208 // value: "<last_file_id>" | |
209 if (last_file_id_ >= 0 || | |
210 !base::StringToInt64(itr->value().ToString(), &last_file_id_)) | |
211 return false; | |
212 | |
213 if (last_file_id_ < 0) | |
214 return false; | |
215 } else if (key == kLastIntegerKey) { | |
216 // key: "LAST_INTEGER" | |
217 // value: "<last_integer>" | |
218 if (last_integer_ >= 0 || | |
219 !base::StringToInt64(itr->value().ToString(), &last_integer_)) | |
220 return false; | |
221 } else { | |
222 // key: "<entry_id>" | |
223 // value: "<pickled FileInfo>" | |
224 FileInfo file_info; | |
225 if (!FileInfoFromPickle( | |
226 Pickle(itr->value().data(), itr->value().size()), &file_info)) | |
227 return false; | |
228 | |
229 FileId file_id = -1; | |
230 if (!base::StringToInt64(key, &file_id) || file_id < 0) | |
231 return false; | |
232 | |
233 if (max_file_id < file_id) | |
234 max_file_id = file_id; | |
235 if (!file_ids.insert(file_id).second) | |
236 return false; | |
237 | |
238 if (file_info.is_directory()) { | |
239 ++num_directories_in_db_; | |
240 DCHECK(file_info.data_path.empty()); | |
241 } else { | |
242 // Ensure any pair of file entry don't share their data_path. | |
243 if (!files_in_db_.insert(file_info.data_path).second) | |
244 return false; | |
245 | |
246 // Ensure the backing file exists as a normal file. | |
247 base::File::Info platform_file_info; | |
248 if (!base::GetFileInfo( | |
249 path_.Append(file_info.data_path), &platform_file_info) || | |
250 platform_file_info.is_directory || | |
251 platform_file_info.is_symbolic_link) { | |
252 // leveldb::Iterator iterates a snapshot of the database. | |
253 // So even after RemoveFileInfo() call, we'll visit hierarchy link | |
254 // from |parent_id| to |file_id|. | |
255 if (!dir_db_->RemoveFileInfo(file_id)) | |
256 return false; | |
257 --num_hierarchy_links_in_db_; | |
258 files_in_db_.erase(file_info.data_path); | |
259 } else { | |
260 ++num_files_in_db_; | |
261 } | |
262 } | |
263 } | |
264 } | |
265 | |
266 // TODO(tzik): Add constraint for |last_integer_| to avoid possible | |
267 // data path confliction on ObfuscatedFileUtil. | |
268 return max_file_id <= last_file_id_; | |
269 } | |
270 | |
271 bool DatabaseCheckHelper::ScanDirectory() { | |
272 // TODO(kinuko): Scans all local file system entries to verify each of them | |
273 // has a database entry. | |
274 const base::FilePath kExcludes[] = { | |
275 base::FilePath(kDirectoryDatabaseName), | |
276 base::FilePath(storage::FileSystemUsageCache::kUsageFileName), | |
277 }; | |
278 | |
279 // Any path in |pending_directories| is relative to |path_|. | |
280 std::stack<base::FilePath> pending_directories; | |
281 pending_directories.push(base::FilePath()); | |
282 | |
283 while (!pending_directories.empty()) { | |
284 base::FilePath dir_path = pending_directories.top(); | |
285 pending_directories.pop(); | |
286 | |
287 base::FileEnumerator file_enum( | |
288 dir_path.empty() ? path_ : path_.Append(dir_path), | |
289 false /* not recursive */, | |
290 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); | |
291 | |
292 base::FilePath absolute_file_path; | |
293 while (!(absolute_file_path = file_enum.Next()).empty()) { | |
294 base::FileEnumerator::FileInfo find_info = file_enum.GetInfo(); | |
295 | |
296 base::FilePath relative_file_path; | |
297 if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path)) | |
298 return false; | |
299 | |
300 if (std::find(kExcludes, kExcludes + arraysize(kExcludes), | |
301 relative_file_path) != kExcludes + arraysize(kExcludes)) | |
302 continue; | |
303 | |
304 if (find_info.IsDirectory()) { | |
305 pending_directories.push(relative_file_path); | |
306 continue; | |
307 } | |
308 | |
309 // Check if the file has a database entry. | |
310 std::set<base::FilePath>::iterator itr = | |
311 files_in_db_.find(relative_file_path); | |
312 if (itr == files_in_db_.end()) { | |
313 if (!base::DeleteFile(absolute_file_path, false)) | |
314 return false; | |
315 } else { | |
316 files_in_db_.erase(itr); | |
317 } | |
318 } | |
319 } | |
320 | |
321 return files_in_db_.empty(); | |
322 } | |
323 | |
324 bool DatabaseCheckHelper::ScanHierarchy() { | |
325 size_t visited_directories = 0; | |
326 size_t visited_files = 0; | |
327 size_t visited_links = 0; | |
328 | |
329 std::stack<FileId> directories; | |
330 directories.push(0); | |
331 | |
332 // Check if the root directory exists as a directory. | |
333 FileInfo file_info; | |
334 if (!dir_db_->GetFileInfo(0, &file_info)) | |
335 return false; | |
336 if (file_info.parent_id != 0 || | |
337 !file_info.is_directory()) | |
338 return false; | |
339 | |
340 while (!directories.empty()) { | |
341 ++visited_directories; | |
342 FileId dir_id = directories.top(); | |
343 directories.pop(); | |
344 | |
345 std::vector<FileId> children; | |
346 if (!dir_db_->ListChildren(dir_id, &children)) | |
347 return false; | |
348 for (std::vector<FileId>::iterator itr = children.begin(); | |
349 itr != children.end(); | |
350 ++itr) { | |
351 // Any directory must not have root directory as child. | |
352 if (!*itr) | |
353 return false; | |
354 | |
355 // Check if the child knows the parent as its parent. | |
356 FileInfo file_info; | |
357 if (!dir_db_->GetFileInfo(*itr, &file_info)) | |
358 return false; | |
359 if (file_info.parent_id != dir_id) | |
360 return false; | |
361 | |
362 // Check if the parent knows the name of its child correctly. | |
363 FileId file_id; | |
364 if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) || | |
365 file_id != *itr) | |
366 return false; | |
367 | |
368 if (file_info.is_directory()) | |
369 directories.push(*itr); | |
370 else | |
371 ++visited_files; | |
372 ++visited_links; | |
373 } | |
374 } | |
375 | |
376 // Check if we've visited all database entries. | |
377 return num_directories_in_db_ == visited_directories && | |
378 num_files_in_db_ == visited_files && | |
379 num_hierarchy_links_in_db_ == visited_links; | |
380 } | |
381 | |
382 // Returns true if the given |data_path| contains no parent references ("..") | |
383 // and does not refer to special system files. | |
384 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to | |
385 // ensure we're only dealing with valid data paths. | |
386 bool VerifyDataPath(const base::FilePath& data_path) { | |
387 // |data_path| should not contain any ".." and should be a relative path | |
388 // (to the filesystem_data_directory_). | |
389 if (data_path.ReferencesParent() || data_path.IsAbsolute()) | |
390 return false; | |
391 // See if it's not pointing to the special system paths. | |
392 const base::FilePath kExcludes[] = { | |
393 base::FilePath(kDirectoryDatabaseName), | |
394 base::FilePath(storage::FileSystemUsageCache::kUsageFileName), | |
395 }; | |
396 for (size_t i = 0; i < arraysize(kExcludes); ++i) { | |
397 if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path)) | |
398 return false; | |
399 } | |
400 return true; | |
401 } | |
402 | |
403 } // namespace | |
404 | |
405 namespace storage { | |
406 | |
407 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) { | |
408 } | |
409 | |
410 SandboxDirectoryDatabase::FileInfo::~FileInfo() { | |
411 } | |
412 | |
413 SandboxDirectoryDatabase::SandboxDirectoryDatabase( | |
414 const base::FilePath& filesystem_data_directory, | |
415 leveldb::Env* env_override) | |
416 : filesystem_data_directory_(filesystem_data_directory), | |
417 env_override_(env_override) { | |
418 } | |
419 | |
420 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() { | |
421 } | |
422 | |
423 bool SandboxDirectoryDatabase::GetChildWithName( | |
424 FileId parent_id, | |
425 const base::FilePath::StringType& name, | |
426 FileId* child_id) { | |
427 if (!Init(REPAIR_ON_CORRUPTION)) | |
428 return false; | |
429 DCHECK(child_id); | |
430 std::string child_key = GetChildLookupKey(parent_id, name); | |
431 std::string child_id_string; | |
432 leveldb::Status status = | |
433 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | |
434 if (status.IsNotFound()) | |
435 return false; | |
436 if (status.ok()) { | |
437 if (!base::StringToInt64(child_id_string, child_id)) { | |
438 LOG(ERROR) << "Hit database corruption!"; | |
439 return false; | |
440 } | |
441 return true; | |
442 } | |
443 HandleError(FROM_HERE, status); | |
444 return false; | |
445 } | |
446 | |
447 bool SandboxDirectoryDatabase::GetFileWithPath( | |
448 const base::FilePath& path, FileId* file_id) { | |
449 std::vector<base::FilePath::StringType> components; | |
450 VirtualPath::GetComponents(path, &components); | |
451 FileId local_id = 0; | |
452 std::vector<base::FilePath::StringType>::iterator iter; | |
453 for (iter = components.begin(); iter != components.end(); ++iter) { | |
454 base::FilePath::StringType name; | |
455 name = *iter; | |
456 if (name == FILE_PATH_LITERAL("/")) | |
457 continue; | |
458 if (!GetChildWithName(local_id, name, &local_id)) | |
459 return false; | |
460 } | |
461 *file_id = local_id; | |
462 return true; | |
463 } | |
464 | |
465 bool SandboxDirectoryDatabase::ListChildren( | |
466 FileId parent_id, std::vector<FileId>* children) { | |
467 // Check to add later: fail if parent is a file, at least in debug builds. | |
468 if (!Init(REPAIR_ON_CORRUPTION)) | |
469 return false; | |
470 DCHECK(children); | |
471 std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); | |
472 | |
473 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | |
474 iter->Seek(child_key_prefix); | |
475 children->clear(); | |
476 while (iter->Valid() && | |
477 StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) { | |
478 std::string child_id_string = iter->value().ToString(); | |
479 FileId child_id; | |
480 if (!base::StringToInt64(child_id_string, &child_id)) { | |
481 LOG(ERROR) << "Hit database corruption!"; | |
482 return false; | |
483 } | |
484 children->push_back(child_id); | |
485 iter->Next(); | |
486 } | |
487 return true; | |
488 } | |
489 | |
490 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { | |
491 if (!Init(REPAIR_ON_CORRUPTION)) | |
492 return false; | |
493 DCHECK(info); | |
494 std::string file_key = GetFileLookupKey(file_id); | |
495 std::string file_data_string; | |
496 leveldb::Status status = | |
497 db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); | |
498 if (status.ok()) { | |
499 bool success = FileInfoFromPickle( | |
500 Pickle(file_data_string.data(), file_data_string.length()), info); | |
501 if (!success) | |
502 return false; | |
503 if (!VerifyDataPath(info->data_path)) { | |
504 LOG(ERROR) << "Resolved data path is invalid: " | |
505 << info->data_path.value(); | |
506 return false; | |
507 } | |
508 return true; | |
509 } | |
510 // Special-case the root, for databases that haven't been initialized yet. | |
511 // Without this, a query for the root's file info, made before creating the | |
512 // first file in the database, will fail and confuse callers. | |
513 if (status.IsNotFound() && !file_id) { | |
514 info->name = base::FilePath::StringType(); | |
515 info->data_path = base::FilePath(); | |
516 info->modification_time = base::Time::Now(); | |
517 info->parent_id = 0; | |
518 return true; | |
519 } | |
520 HandleError(FROM_HERE, status); | |
521 return false; | |
522 } | |
523 | |
524 base::File::Error SandboxDirectoryDatabase::AddFileInfo( | |
525 const FileInfo& info, FileId* file_id) { | |
526 if (!Init(REPAIR_ON_CORRUPTION)) | |
527 return base::File::FILE_ERROR_FAILED; | |
528 DCHECK(file_id); | |
529 std::string child_key = GetChildLookupKey(info.parent_id, info.name); | |
530 std::string child_id_string; | |
531 leveldb::Status status = | |
532 db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); | |
533 if (status.ok()) { | |
534 LOG(ERROR) << "File exists already!"; | |
535 return base::File::FILE_ERROR_EXISTS; | |
536 } | |
537 if (!status.IsNotFound()) { | |
538 HandleError(FROM_HERE, status); | |
539 return base::File::FILE_ERROR_NOT_FOUND; | |
540 } | |
541 | |
542 if (!IsDirectory(info.parent_id)) { | |
543 LOG(ERROR) << "New parent directory is a file!"; | |
544 return base::File::FILE_ERROR_NOT_A_DIRECTORY; | |
545 } | |
546 | |
547 // This would be a fine place to limit the number of files in a directory, if | |
548 // we decide to add that restriction. | |
549 | |
550 FileId temp_id; | |
551 if (!GetLastFileId(&temp_id)) | |
552 return base::File::FILE_ERROR_FAILED; | |
553 ++temp_id; | |
554 | |
555 leveldb::WriteBatch batch; | |
556 if (!AddFileInfoHelper(info, temp_id, &batch)) | |
557 return base::File::FILE_ERROR_FAILED; | |
558 | |
559 batch.Put(LastFileIdKey(), base::Int64ToString(temp_id)); | |
560 status = db_->Write(leveldb::WriteOptions(), &batch); | |
561 if (!status.ok()) { | |
562 HandleError(FROM_HERE, status); | |
563 return base::File::FILE_ERROR_FAILED; | |
564 } | |
565 *file_id = temp_id; | |
566 return base::File::FILE_OK; | |
567 } | |
568 | |
569 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) { | |
570 if (!Init(REPAIR_ON_CORRUPTION)) | |
571 return false; | |
572 leveldb::WriteBatch batch; | |
573 if (!RemoveFileInfoHelper(file_id, &batch)) | |
574 return false; | |
575 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
576 if (!status.ok()) { | |
577 HandleError(FROM_HERE, status); | |
578 return false; | |
579 } | |
580 return true; | |
581 } | |
582 | |
583 bool SandboxDirectoryDatabase::UpdateFileInfo( | |
584 FileId file_id, const FileInfo& new_info) { | |
585 // TODO(ericu): We should also check to see that this doesn't create a loop, | |
586 // but perhaps only in a debug build. | |
587 if (!Init(REPAIR_ON_CORRUPTION)) | |
588 return false; | |
589 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. | |
590 FileInfo old_info; | |
591 if (!GetFileInfo(file_id, &old_info)) | |
592 return false; | |
593 if (old_info.parent_id != new_info.parent_id && | |
594 !IsDirectory(new_info.parent_id)) | |
595 return false; | |
596 if (old_info.parent_id != new_info.parent_id || | |
597 old_info.name != new_info.name) { | |
598 // Check for name clashes. | |
599 FileId temp_id; | |
600 if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) { | |
601 LOG(ERROR) << "Name collision on move."; | |
602 return false; | |
603 } | |
604 } | |
605 leveldb::WriteBatch batch; | |
606 if (!RemoveFileInfoHelper(file_id, &batch) || | |
607 !AddFileInfoHelper(new_info, file_id, &batch)) | |
608 return false; | |
609 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
610 if (!status.ok()) { | |
611 HandleError(FROM_HERE, status); | |
612 return false; | |
613 } | |
614 return true; | |
615 } | |
616 | |
617 bool SandboxDirectoryDatabase::UpdateModificationTime( | |
618 FileId file_id, const base::Time& modification_time) { | |
619 FileInfo info; | |
620 if (!GetFileInfo(file_id, &info)) | |
621 return false; | |
622 info.modification_time = modification_time; | |
623 Pickle pickle; | |
624 if (!PickleFromFileInfo(info, &pickle)) | |
625 return false; | |
626 leveldb::Status status = db_->Put( | |
627 leveldb::WriteOptions(), | |
628 GetFileLookupKey(file_id), | |
629 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | |
630 pickle.size())); | |
631 if (!status.ok()) { | |
632 HandleError(FROM_HERE, status); | |
633 return false; | |
634 } | |
635 return true; | |
636 } | |
637 | |
638 bool SandboxDirectoryDatabase::OverwritingMoveFile( | |
639 FileId src_file_id, FileId dest_file_id) { | |
640 FileInfo src_file_info; | |
641 FileInfo dest_file_info; | |
642 | |
643 if (!GetFileInfo(src_file_id, &src_file_info)) | |
644 return false; | |
645 if (!GetFileInfo(dest_file_id, &dest_file_info)) | |
646 return false; | |
647 if (src_file_info.is_directory() || dest_file_info.is_directory()) | |
648 return false; | |
649 leveldb::WriteBatch batch; | |
650 // This is the only field that really gets moved over; if you add fields to | |
651 // FileInfo, e.g. ctime, they might need to be copied here. | |
652 dest_file_info.data_path = src_file_info.data_path; | |
653 if (!RemoveFileInfoHelper(src_file_id, &batch)) | |
654 return false; | |
655 Pickle pickle; | |
656 if (!PickleFromFileInfo(dest_file_info, &pickle)) | |
657 return false; | |
658 batch.Put( | |
659 GetFileLookupKey(dest_file_id), | |
660 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | |
661 pickle.size())); | |
662 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
663 if (!status.ok()) { | |
664 HandleError(FROM_HERE, status); | |
665 return false; | |
666 } | |
667 return true; | |
668 } | |
669 | |
670 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) { | |
671 if (!Init(REPAIR_ON_CORRUPTION)) | |
672 return false; | |
673 DCHECK(next); | |
674 std::string int_string; | |
675 leveldb::Status status = | |
676 db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); | |
677 if (status.ok()) { | |
678 int64 temp; | |
679 if (!base::StringToInt64(int_string, &temp)) { | |
680 LOG(ERROR) << "Hit database corruption!"; | |
681 return false; | |
682 } | |
683 ++temp; | |
684 status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(), | |
685 base::Int64ToString(temp)); | |
686 if (!status.ok()) { | |
687 HandleError(FROM_HERE, status); | |
688 return false; | |
689 } | |
690 *next = temp; | |
691 return true; | |
692 } | |
693 if (!status.IsNotFound()) { | |
694 HandleError(FROM_HERE, status); | |
695 return false; | |
696 } | |
697 // The database must not yet exist; initialize it. | |
698 if (!StoreDefaultValues()) | |
699 return false; | |
700 | |
701 return GetNextInteger(next); | |
702 } | |
703 | |
704 // static | |
705 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path, | |
706 leveldb::Env* env_override) { | |
707 std::string name = FilePathToString(path.Append(kDirectoryDatabaseName)); | |
708 leveldb::Options options; | |
709 if (env_override) | |
710 options.env = env_override; | |
711 leveldb::Status status = leveldb::DestroyDB(name, options); | |
712 if (status.ok()) | |
713 return true; | |
714 LOG(WARNING) << "Failed to destroy a database with status " << | |
715 status.ToString(); | |
716 return false; | |
717 } | |
718 | |
719 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) { | |
720 if (db_) | |
721 return true; | |
722 | |
723 std::string path = | |
724 FilePathToString(filesystem_data_directory_.Append( | |
725 kDirectoryDatabaseName)); | |
726 leveldb::Options options; | |
727 options.max_open_files = 0; // Use minimum. | |
728 options.create_if_missing = true; | |
729 if (env_override_) | |
730 options.env = env_override_; | |
731 leveldb::DB* db; | |
732 leveldb::Status status = leveldb::DB::Open(options, path, &db); | |
733 ReportInitStatus(status); | |
734 if (status.ok()) { | |
735 db_.reset(db); | |
736 return true; | |
737 } | |
738 HandleError(FROM_HERE, status); | |
739 | |
740 // Corruption due to missing necessary MANIFEST-* file causes IOError instead | |
741 // of Corruption error. | |
742 // Try to repair database even when IOError case. | |
743 if (!status.IsCorruption() && !status.IsIOError()) | |
744 return false; | |
745 | |
746 switch (recovery_option) { | |
747 case FAIL_ON_CORRUPTION: | |
748 return false; | |
749 case REPAIR_ON_CORRUPTION: | |
750 LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected." | |
751 << " Attempting to repair."; | |
752 if (RepairDatabase(path)) { | |
753 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | |
754 DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); | |
755 return true; | |
756 } | |
757 UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, | |
758 DB_REPAIR_FAILED, DB_REPAIR_MAX); | |
759 LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase."; | |
760 // fall through | |
761 case DELETE_ON_CORRUPTION: | |
762 LOG(WARNING) << "Clearing SandboxDirectoryDatabase."; | |
763 if (!base::DeleteFile(filesystem_data_directory_, true)) | |
764 return false; | |
765 if (!base::CreateDirectory(filesystem_data_directory_)) | |
766 return false; | |
767 return Init(FAIL_ON_CORRUPTION); | |
768 } | |
769 | |
770 NOTREACHED(); | |
771 return false; | |
772 } | |
773 | |
774 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) { | |
775 DCHECK(!db_.get()); | |
776 leveldb::Options options; | |
777 options.max_open_files = 0; // Use minimum. | |
778 if (env_override_) | |
779 options.env = env_override_; | |
780 if (!leveldb::RepairDB(db_path, options).ok()) | |
781 return false; | |
782 if (!Init(FAIL_ON_CORRUPTION)) | |
783 return false; | |
784 if (IsFileSystemConsistent()) | |
785 return true; | |
786 db_.reset(); | |
787 return false; | |
788 } | |
789 | |
790 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) { | |
791 FileInfo info; | |
792 if (!file_id) | |
793 return true; // The root is a directory. | |
794 if (!GetFileInfo(file_id, &info)) | |
795 return false; | |
796 if (!info.is_directory()) | |
797 return false; | |
798 return true; | |
799 } | |
800 | |
801 bool SandboxDirectoryDatabase::IsFileSystemConsistent() { | |
802 if (!Init(FAIL_ON_CORRUPTION)) | |
803 return false; | |
804 DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_); | |
805 return helper.IsFileSystemConsistent(); | |
806 } | |
807 | |
808 void SandboxDirectoryDatabase::ReportInitStatus( | |
809 const leveldb::Status& status) { | |
810 base::Time now = base::Time::Now(); | |
811 const base::TimeDelta minimum_interval = | |
812 base::TimeDelta::FromHours(kMinimumReportIntervalHours); | |
813 if (last_reported_time_ + minimum_interval >= now) | |
814 return; | |
815 last_reported_time_ = now; | |
816 | |
817 if (status.ok()) { | |
818 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
819 INIT_STATUS_OK, INIT_STATUS_MAX); | |
820 } else if (status.IsCorruption()) { | |
821 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
822 INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); | |
823 } else if (status.IsIOError()) { | |
824 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
825 INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); | |
826 } else { | |
827 UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, | |
828 INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); | |
829 } | |
830 } | |
831 | |
832 bool SandboxDirectoryDatabase::StoreDefaultValues() { | |
833 // Verify that this is a totally new database, and initialize it. | |
834 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | |
835 iter->SeekToFirst(); | |
836 if (iter->Valid()) { // DB was not empty--we shouldn't have been called. | |
837 LOG(ERROR) << "File system origin database is corrupt!"; | |
838 return false; | |
839 } | |
840 // This is always the first write into the database. If we ever add a | |
841 // version number, it should go in this transaction too. | |
842 FileInfo root; | |
843 root.parent_id = 0; | |
844 root.modification_time = base::Time::Now(); | |
845 leveldb::WriteBatch batch; | |
846 if (!AddFileInfoHelper(root, 0, &batch)) | |
847 return false; | |
848 batch.Put(LastFileIdKey(), base::Int64ToString(0)); | |
849 batch.Put(LastIntegerKey(), base::Int64ToString(-1)); | |
850 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
851 if (!status.ok()) { | |
852 HandleError(FROM_HERE, status); | |
853 return false; | |
854 } | |
855 return true; | |
856 } | |
857 | |
858 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) { | |
859 if (!Init(REPAIR_ON_CORRUPTION)) | |
860 return false; | |
861 DCHECK(file_id); | |
862 std::string id_string; | |
863 leveldb::Status status = | |
864 db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); | |
865 if (status.ok()) { | |
866 if (!base::StringToInt64(id_string, file_id)) { | |
867 LOG(ERROR) << "Hit database corruption!"; | |
868 return false; | |
869 } | |
870 return true; | |
871 } | |
872 if (!status.IsNotFound()) { | |
873 HandleError(FROM_HERE, status); | |
874 return false; | |
875 } | |
876 // The database must not yet exist; initialize it. | |
877 if (!StoreDefaultValues()) | |
878 return false; | |
879 *file_id = 0; | |
880 return true; | |
881 } | |
882 | |
883 // This does very few safety checks! | |
884 bool SandboxDirectoryDatabase::AddFileInfoHelper( | |
885 const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) { | |
886 if (!VerifyDataPath(info.data_path)) { | |
887 LOG(ERROR) << "Invalid data path is given: " << info.data_path.value(); | |
888 return false; | |
889 } | |
890 std::string id_string = GetFileLookupKey(file_id); | |
891 if (!file_id) { | |
892 // The root directory doesn't need to be looked up by path from its parent. | |
893 DCHECK(!info.parent_id); | |
894 DCHECK(info.data_path.empty()); | |
895 } else { | |
896 std::string child_key = GetChildLookupKey(info.parent_id, info.name); | |
897 batch->Put(child_key, id_string); | |
898 } | |
899 Pickle pickle; | |
900 if (!PickleFromFileInfo(info, &pickle)) | |
901 return false; | |
902 batch->Put( | |
903 id_string, | |
904 leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), | |
905 pickle.size())); | |
906 return true; | |
907 } | |
908 | |
909 // This does very few safety checks! | |
910 bool SandboxDirectoryDatabase::RemoveFileInfoHelper( | |
911 FileId file_id, leveldb::WriteBatch* batch) { | |
912 DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. | |
913 FileInfo info; | |
914 if (!GetFileInfo(file_id, &info)) | |
915 return false; | |
916 if (info.data_path.empty()) { // It's a directory | |
917 std::vector<FileId> children; | |
918 // TODO(ericu): Make a faster is-the-directory-empty check. | |
919 if (!ListChildren(file_id, &children)) | |
920 return false; | |
921 if (children.size()) { | |
922 LOG(ERROR) << "Can't remove a directory with children."; | |
923 return false; | |
924 } | |
925 } | |
926 batch->Delete(GetChildLookupKey(info.parent_id, info.name)); | |
927 batch->Delete(GetFileLookupKey(file_id)); | |
928 return true; | |
929 } | |
930 | |
931 void SandboxDirectoryDatabase::HandleError( | |
932 const tracked_objects::Location& from_here, | |
933 const leveldb::Status& status) { | |
934 LOG(ERROR) << "SandboxDirectoryDatabase failed at: " | |
935 << from_here.ToString() << " with error: " << status.ToString(); | |
936 db_.reset(); | |
937 } | |
938 | |
939 } // namespace storage | |
OLD | NEW |