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