• 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::File::Info 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,leveldb::Env * env_override)412 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
413     const base::FilePath& filesystem_data_directory,
414     leveldb::Env* env_override)
415     : filesystem_data_directory_(filesystem_data_directory),
416       env_override_(env_override) {
417 }
418 
~SandboxDirectoryDatabase()419 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
420 }
421 
GetChildWithName(FileId parent_id,const base::FilePath::StringType & name,FileId * child_id)422 bool SandboxDirectoryDatabase::GetChildWithName(
423     FileId parent_id,
424     const base::FilePath::StringType& name,
425     FileId* child_id) {
426   if (!Init(REPAIR_ON_CORRUPTION))
427     return false;
428   DCHECK(child_id);
429   std::string child_key = GetChildLookupKey(parent_id, name);
430   std::string child_id_string;
431   leveldb::Status status =
432       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
433   if (status.IsNotFound())
434     return false;
435   if (status.ok()) {
436     if (!base::StringToInt64(child_id_string, child_id)) {
437       LOG(ERROR) << "Hit database corruption!";
438       return false;
439     }
440     return true;
441   }
442   HandleError(FROM_HERE, status);
443   return false;
444 }
445 
GetFileWithPath(const base::FilePath & path,FileId * file_id)446 bool SandboxDirectoryDatabase::GetFileWithPath(
447     const base::FilePath& path, FileId* file_id) {
448   std::vector<base::FilePath::StringType> components;
449   VirtualPath::GetComponents(path, &components);
450   FileId local_id = 0;
451   std::vector<base::FilePath::StringType>::iterator iter;
452   for (iter = components.begin(); iter != components.end(); ++iter) {
453     base::FilePath::StringType name;
454     name = *iter;
455     if (name == FILE_PATH_LITERAL("/"))
456       continue;
457     if (!GetChildWithName(local_id, name, &local_id))
458       return false;
459   }
460   *file_id = local_id;
461   return true;
462 }
463 
ListChildren(FileId parent_id,std::vector<FileId> * children)464 bool SandboxDirectoryDatabase::ListChildren(
465     FileId parent_id, std::vector<FileId>* children) {
466   // Check to add later: fail if parent is a file, at least in debug builds.
467   if (!Init(REPAIR_ON_CORRUPTION))
468     return false;
469   DCHECK(children);
470   std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
471 
472   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
473   iter->Seek(child_key_prefix);
474   children->clear();
475   while (iter->Valid() &&
476       StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
477     std::string child_id_string = iter->value().ToString();
478     FileId child_id;
479     if (!base::StringToInt64(child_id_string, &child_id)) {
480       LOG(ERROR) << "Hit database corruption!";
481       return false;
482     }
483     children->push_back(child_id);
484     iter->Next();
485   }
486   return true;
487 }
488 
GetFileInfo(FileId file_id,FileInfo * info)489 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
490   if (!Init(REPAIR_ON_CORRUPTION))
491     return false;
492   DCHECK(info);
493   std::string file_key = GetFileLookupKey(file_id);
494   std::string file_data_string;
495   leveldb::Status status =
496       db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
497   if (status.ok()) {
498     bool success = FileInfoFromPickle(
499         Pickle(file_data_string.data(), file_data_string.length()), info);
500     if (!success)
501       return false;
502     if (!VerifyDataPath(info->data_path)) {
503       LOG(ERROR) << "Resolved data path is invalid: "
504                  << info->data_path.value();
505       return false;
506     }
507     return true;
508   }
509   // Special-case the root, for databases that haven't been initialized yet.
510   // Without this, a query for the root's file info, made before creating the
511   // first file in the database, will fail and confuse callers.
512   if (status.IsNotFound() && !file_id) {
513     info->name = base::FilePath::StringType();
514     info->data_path = base::FilePath();
515     info->modification_time = base::Time::Now();
516     info->parent_id = 0;
517     return true;
518   }
519   HandleError(FROM_HERE, status);
520   return false;
521 }
522 
AddFileInfo(const FileInfo & info,FileId * file_id)523 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
524     const FileInfo& info, FileId* file_id) {
525   if (!Init(REPAIR_ON_CORRUPTION))
526     return base::File::FILE_ERROR_FAILED;
527   DCHECK(file_id);
528   std::string child_key = GetChildLookupKey(info.parent_id, info.name);
529   std::string child_id_string;
530   leveldb::Status status =
531       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
532   if (status.ok()) {
533     LOG(ERROR) << "File exists already!";
534     return base::File::FILE_ERROR_EXISTS;
535   }
536   if (!status.IsNotFound()) {
537     HandleError(FROM_HERE, status);
538     return base::File::FILE_ERROR_NOT_FOUND;
539   }
540 
541   if (!IsDirectory(info.parent_id)) {
542     LOG(ERROR) << "New parent directory is a file!";
543     return base::File::FILE_ERROR_NOT_A_DIRECTORY;
544   }
545 
546   // This would be a fine place to limit the number of files in a directory, if
547   // we decide to add that restriction.
548 
549   FileId temp_id;
550   if (!GetLastFileId(&temp_id))
551     return base::File::FILE_ERROR_FAILED;
552   ++temp_id;
553 
554   leveldb::WriteBatch batch;
555   if (!AddFileInfoHelper(info, temp_id, &batch))
556     return base::File::FILE_ERROR_FAILED;
557 
558   batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
559   status = db_->Write(leveldb::WriteOptions(), &batch);
560   if (!status.ok()) {
561     HandleError(FROM_HERE, status);
562     return base::File::FILE_ERROR_FAILED;
563   }
564   *file_id = temp_id;
565   return base::File::FILE_OK;
566 }
567 
RemoveFileInfo(FileId file_id)568 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
569   if (!Init(REPAIR_ON_CORRUPTION))
570     return false;
571   leveldb::WriteBatch batch;
572   if (!RemoveFileInfoHelper(file_id, &batch))
573     return false;
574   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
575   if (!status.ok()) {
576     HandleError(FROM_HERE, status);
577     return false;
578   }
579   return true;
580 }
581 
UpdateFileInfo(FileId file_id,const FileInfo & new_info)582 bool SandboxDirectoryDatabase::UpdateFileInfo(
583     FileId file_id, const FileInfo& new_info) {
584   // TODO(ericu): We should also check to see that this doesn't create a loop,
585   // but perhaps only in a debug build.
586   if (!Init(REPAIR_ON_CORRUPTION))
587     return false;
588   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
589   FileInfo old_info;
590   if (!GetFileInfo(file_id, &old_info))
591     return false;
592   if (old_info.parent_id != new_info.parent_id &&
593       !IsDirectory(new_info.parent_id))
594     return false;
595   if (old_info.parent_id != new_info.parent_id ||
596       old_info.name != new_info.name) {
597     // Check for name clashes.
598     FileId temp_id;
599     if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
600       LOG(ERROR) << "Name collision on move.";
601       return false;
602     }
603   }
604   leveldb::WriteBatch batch;
605   if (!RemoveFileInfoHelper(file_id, &batch) ||
606       !AddFileInfoHelper(new_info, file_id, &batch))
607     return false;
608   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
609   if (!status.ok()) {
610     HandleError(FROM_HERE, status);
611     return false;
612   }
613   return true;
614 }
615 
UpdateModificationTime(FileId file_id,const base::Time & modification_time)616 bool SandboxDirectoryDatabase::UpdateModificationTime(
617     FileId file_id, const base::Time& modification_time) {
618   FileInfo info;
619   if (!GetFileInfo(file_id, &info))
620     return false;
621   info.modification_time = modification_time;
622   Pickle pickle;
623   if (!PickleFromFileInfo(info, &pickle))
624     return false;
625   leveldb::Status status = db_->Put(
626       leveldb::WriteOptions(),
627       GetFileLookupKey(file_id),
628       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
629                      pickle.size()));
630   if (!status.ok()) {
631     HandleError(FROM_HERE, status);
632     return false;
633   }
634   return true;
635 }
636 
OverwritingMoveFile(FileId src_file_id,FileId dest_file_id)637 bool SandboxDirectoryDatabase::OverwritingMoveFile(
638     FileId src_file_id, FileId dest_file_id) {
639   FileInfo src_file_info;
640   FileInfo dest_file_info;
641 
642   if (!GetFileInfo(src_file_id, &src_file_info))
643     return false;
644   if (!GetFileInfo(dest_file_id, &dest_file_info))
645     return false;
646   if (src_file_info.is_directory() || dest_file_info.is_directory())
647     return false;
648   leveldb::WriteBatch batch;
649   // This is the only field that really gets moved over; if you add fields to
650   // FileInfo, e.g. ctime, they might need to be copied here.
651   dest_file_info.data_path = src_file_info.data_path;
652   if (!RemoveFileInfoHelper(src_file_id, &batch))
653     return false;
654   Pickle pickle;
655   if (!PickleFromFileInfo(dest_file_info, &pickle))
656     return false;
657   batch.Put(
658       GetFileLookupKey(dest_file_id),
659       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
660                      pickle.size()));
661   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
662   if (!status.ok()) {
663     HandleError(FROM_HERE, status);
664     return false;
665   }
666   return true;
667 }
668 
GetNextInteger(int64 * next)669 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
670   if (!Init(REPAIR_ON_CORRUPTION))
671     return false;
672   DCHECK(next);
673   std::string int_string;
674   leveldb::Status status =
675       db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
676   if (status.ok()) {
677     int64 temp;
678     if (!base::StringToInt64(int_string, &temp)) {
679       LOG(ERROR) << "Hit database corruption!";
680       return false;
681     }
682     ++temp;
683     status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
684         base::Int64ToString(temp));
685     if (!status.ok()) {
686       HandleError(FROM_HERE, status);
687       return false;
688     }
689     *next = temp;
690     return true;
691   }
692   if (!status.IsNotFound()) {
693     HandleError(FROM_HERE, status);
694     return false;
695   }
696   // The database must not yet exist; initialize it.
697   if (!StoreDefaultValues())
698     return false;
699 
700   return GetNextInteger(next);
701 }
702 
703 // static
DestroyDatabase(const base::FilePath & path,leveldb::Env * env_override)704 bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path,
705                                                leveldb::Env* env_override) {
706   std::string name  = FilePathToString(path.Append(kDirectoryDatabaseName));
707   leveldb::Options options;
708   if (env_override)
709     options.env = env_override;
710   leveldb::Status status = leveldb::DestroyDB(name, options);
711   if (status.ok())
712     return true;
713   LOG(WARNING) << "Failed to destroy a database with status " <<
714       status.ToString();
715   return false;
716 }
717 
Init(RecoveryOption recovery_option)718 bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
719   if (db_)
720     return true;
721 
722   std::string path =
723       FilePathToString(filesystem_data_directory_.Append(
724           kDirectoryDatabaseName));
725   leveldb::Options options;
726   options.max_open_files = 0;  // Use minimum.
727   options.create_if_missing = true;
728   if (env_override_)
729     options.env = env_override_;
730   leveldb::DB* db;
731   leveldb::Status status = leveldb::DB::Open(options, path, &db);
732   ReportInitStatus(status);
733   if (status.ok()) {
734     db_.reset(db);
735     return true;
736   }
737   HandleError(FROM_HERE, status);
738 
739   // Corruption due to missing necessary MANIFEST-* file causes IOError instead
740   // of Corruption error.
741   // Try to repair database even when IOError case.
742   if (!status.IsCorruption() && !status.IsIOError())
743     return false;
744 
745   switch (recovery_option) {
746     case FAIL_ON_CORRUPTION:
747       return false;
748     case REPAIR_ON_CORRUPTION:
749       LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
750                    << " Attempting to repair.";
751       if (RepairDatabase(path)) {
752         UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
753                                   DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
754         return true;
755       }
756       UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
757                                 DB_REPAIR_FAILED, DB_REPAIR_MAX);
758       LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
759       // fall through
760     case DELETE_ON_CORRUPTION:
761       LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
762       if (!base::DeleteFile(filesystem_data_directory_, true))
763         return false;
764       if (!base::CreateDirectory(filesystem_data_directory_))
765         return false;
766       return Init(FAIL_ON_CORRUPTION);
767   }
768 
769   NOTREACHED();
770   return false;
771 }
772 
RepairDatabase(const std::string & db_path)773 bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
774   DCHECK(!db_.get());
775   leveldb::Options options;
776   options.max_open_files = 0;  // Use minimum.
777   if (env_override_)
778     options.env = env_override_;
779   if (!leveldb::RepairDB(db_path, options).ok())
780     return false;
781   if (!Init(FAIL_ON_CORRUPTION))
782     return false;
783   if (IsFileSystemConsistent())
784     return true;
785   db_.reset();
786   return false;
787 }
788 
IsDirectory(FileId file_id)789 bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) {
790   FileInfo info;
791   if (!file_id)
792     return true;  // The root is a directory.
793   if (!GetFileInfo(file_id, &info))
794     return false;
795   if (!info.is_directory())
796     return false;
797   return true;
798 }
799 
IsFileSystemConsistent()800 bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
801   if (!Init(FAIL_ON_CORRUPTION))
802     return false;
803   DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
804   return helper.IsFileSystemConsistent();
805 }
806 
ReportInitStatus(const leveldb::Status & status)807 void SandboxDirectoryDatabase::ReportInitStatus(
808     const leveldb::Status& status) {
809   base::Time now = base::Time::Now();
810   const base::TimeDelta minimum_interval =
811       base::TimeDelta::FromHours(kMinimumReportIntervalHours);
812   if (last_reported_time_ + minimum_interval >= now)
813     return;
814   last_reported_time_ = now;
815 
816   if (status.ok()) {
817     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
818                               INIT_STATUS_OK, INIT_STATUS_MAX);
819   } else if (status.IsCorruption()) {
820     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
821                               INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
822   } else if (status.IsIOError()) {
823     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
824                               INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
825   } else {
826     UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
827                               INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
828   }
829 }
830 
StoreDefaultValues()831 bool SandboxDirectoryDatabase::StoreDefaultValues() {
832   // Verify that this is a totally new database, and initialize it.
833   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
834   iter->SeekToFirst();
835   if (iter->Valid()) {  // DB was not empty--we shouldn't have been called.
836     LOG(ERROR) << "File system origin database is corrupt!";
837     return false;
838   }
839   // This is always the first write into the database.  If we ever add a
840   // version number, it should go in this transaction too.
841   FileInfo root;
842   root.parent_id = 0;
843   root.modification_time = base::Time::Now();
844   leveldb::WriteBatch batch;
845   if (!AddFileInfoHelper(root, 0, &batch))
846     return false;
847   batch.Put(LastFileIdKey(), base::Int64ToString(0));
848   batch.Put(LastIntegerKey(), base::Int64ToString(-1));
849   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
850   if (!status.ok()) {
851     HandleError(FROM_HERE, status);
852     return false;
853   }
854   return true;
855 }
856 
GetLastFileId(FileId * file_id)857 bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
858   if (!Init(REPAIR_ON_CORRUPTION))
859     return false;
860   DCHECK(file_id);
861   std::string id_string;
862   leveldb::Status status =
863       db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
864   if (status.ok()) {
865     if (!base::StringToInt64(id_string, file_id)) {
866       LOG(ERROR) << "Hit database corruption!";
867       return false;
868     }
869     return true;
870   }
871   if (!status.IsNotFound()) {
872     HandleError(FROM_HERE, status);
873     return false;
874   }
875   // The database must not yet exist; initialize it.
876   if (!StoreDefaultValues())
877     return false;
878   *file_id = 0;
879   return true;
880 }
881 
882 // This does very few safety checks!
AddFileInfoHelper(const FileInfo & info,FileId file_id,leveldb::WriteBatch * batch)883 bool SandboxDirectoryDatabase::AddFileInfoHelper(
884     const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
885   if (!VerifyDataPath(info.data_path)) {
886     LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
887     return false;
888   }
889   std::string id_string = GetFileLookupKey(file_id);
890   if (!file_id) {
891     // The root directory doesn't need to be looked up by path from its parent.
892     DCHECK(!info.parent_id);
893     DCHECK(info.data_path.empty());
894   } else {
895     std::string child_key = GetChildLookupKey(info.parent_id, info.name);
896     batch->Put(child_key, id_string);
897   }
898   Pickle pickle;
899   if (!PickleFromFileInfo(info, &pickle))
900     return false;
901   batch->Put(
902       id_string,
903       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
904                      pickle.size()));
905   return true;
906 }
907 
908 // This does very few safety checks!
RemoveFileInfoHelper(FileId file_id,leveldb::WriteBatch * batch)909 bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
910     FileId file_id, leveldb::WriteBatch* batch) {
911   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
912   FileInfo info;
913   if (!GetFileInfo(file_id, &info))
914     return false;
915   if (info.data_path.empty()) {  // It's a directory
916     std::vector<FileId> children;
917     // TODO(ericu): Make a faster is-the-directory-empty check.
918     if (!ListChildren(file_id, &children))
919       return false;
920     if (children.size()) {
921       LOG(ERROR) << "Can't remove a directory with children.";
922       return false;
923     }
924   }
925   batch->Delete(GetChildLookupKey(info.parent_id, info.name));
926   batch->Delete(GetFileLookupKey(file_id));
927   return true;
928 }
929 
HandleError(const tracked_objects::Location & from_here,const leveldb::Status & status)930 void SandboxDirectoryDatabase::HandleError(
931     const tracked_objects::Location& from_here,
932     const leveldb::Status& status) {
933   LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
934              << from_here.ToString() << " with error: " << status.ToString();
935   db_.reset();
936 }
937 
938 }  // namespace fileapi
939