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
24namespace {
25
26bool 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
49bool 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
70const base::FilePath::CharType kDirectoryDatabaseName[] =
71    FILE_PATH_LITERAL("Paths");
72const char kChildLookupPrefix[] = "CHILD_OF:";
73const char kChildLookupSeparator[] = ":";
74const char kLastFileIdKey[] = "LAST_FILE_ID";
75const char kLastIntegerKey[] = "LAST_INTEGER";
76const int64 kMinimumReportIntervalHours = 1;
77const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
78const char kDatabaseRepairHistogramLabel[] =
79    "FileSystem.DirectoryDatabaseRepair";
80
81enum 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
89enum RepairResult {
90  DB_REPAIR_SUCCEEDED = 0,
91  DB_REPAIR_FAILED,
92  DB_REPAIR_MAX
93};
94
95std::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
104std::string GetChildListingKeyPrefix(
105    fileapi::SandboxDirectoryDatabase::FileId parent_id) {
106  return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
107      std::string(kChildLookupSeparator);
108}
109
110const char* LastFileIdKey() {
111  return kLastFileIdKey;
112}
113
114const char* LastIntegerKey() {
115  return kLastIntegerKey;
116}
117
118std::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.
135class 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
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
172DatabaseCheckHelper::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
186bool DatabaseCheckHelper::IsDatabaseEmpty() {
187  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
188  itr->SeekToFirst();
189  return !itr->Valid();
190}
191
192bool 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 (!file_util::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
270bool 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
323bool 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.
385bool 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
404namespace fileapi {
405
406SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
407}
408
409SandboxDirectoryDatabase::FileInfo::~FileInfo() {
410}
411
412SandboxDirectoryDatabase::SandboxDirectoryDatabase(
413    const base::FilePath& filesystem_data_directory)
414    : filesystem_data_directory_(filesystem_data_directory) {
415}
416
417SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
418}
419
420bool 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
444bool 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
462bool 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
487bool 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
521bool SandboxDirectoryDatabase::AddFileInfo(
522    const FileInfo& info, FileId* file_id) {
523  if (!Init(REPAIR_ON_CORRUPTION))
524    return false;
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 false;
533  }
534  if (!status.IsNotFound()) {
535    HandleError(FROM_HERE, status);
536    return false;
537  }
538
539  if (!VerifyIsDirectory(info.parent_id))
540    return false;
541
542  // This would be a fine place to limit the number of files in a directory, if
543  // we decide to add that restriction.
544
545  FileId temp_id;
546  if (!GetLastFileId(&temp_id))
547    return false;
548  ++temp_id;
549
550  leveldb::WriteBatch batch;
551  if (!AddFileInfoHelper(info, temp_id, &batch))
552    return false;
553
554  batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
555  status = db_->Write(leveldb::WriteOptions(), &batch);
556  if (!status.ok()) {
557    HandleError(FROM_HERE, status);
558    return false;
559  }
560  *file_id = temp_id;
561  return true;
562}
563
564bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
565  if (!Init(REPAIR_ON_CORRUPTION))
566    return false;
567  leveldb::WriteBatch batch;
568  if (!RemoveFileInfoHelper(file_id, &batch))
569    return false;
570  leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
571  if (!status.ok()) {
572    HandleError(FROM_HERE, status);
573    return false;
574  }
575  return true;
576}
577
578bool SandboxDirectoryDatabase::UpdateFileInfo(
579    FileId file_id, const FileInfo& new_info) {
580  // TODO(ericu): We should also check to see that this doesn't create a loop,
581  // but perhaps only in a debug build.
582  if (!Init(REPAIR_ON_CORRUPTION))
583    return false;
584  DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
585  FileInfo old_info;
586  if (!GetFileInfo(file_id, &old_info))
587    return false;
588  if (old_info.parent_id != new_info.parent_id &&
589      !VerifyIsDirectory(new_info.parent_id))
590    return false;
591  if (old_info.parent_id != new_info.parent_id ||
592      old_info.name != new_info.name) {
593    // Check for name clashes.
594    FileId temp_id;
595    if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
596      LOG(ERROR) << "Name collision on move.";
597      return false;
598    }
599  }
600  leveldb::WriteBatch batch;
601  if (!RemoveFileInfoHelper(file_id, &batch) ||
602      !AddFileInfoHelper(new_info, file_id, &batch))
603    return false;
604  leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
605  if (!status.ok()) {
606    HandleError(FROM_HERE, status);
607    return false;
608  }
609  return true;
610}
611
612bool SandboxDirectoryDatabase::UpdateModificationTime(
613    FileId file_id, const base::Time& modification_time) {
614  FileInfo info;
615  if (!GetFileInfo(file_id, &info))
616    return false;
617  info.modification_time = modification_time;
618  Pickle pickle;
619  if (!PickleFromFileInfo(info, &pickle))
620    return false;
621  leveldb::Status status = db_->Put(
622      leveldb::WriteOptions(),
623      GetFileLookupKey(file_id),
624      leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
625                     pickle.size()));
626  if (!status.ok()) {
627    HandleError(FROM_HERE, status);
628    return false;
629  }
630  return true;
631}
632
633bool SandboxDirectoryDatabase::OverwritingMoveFile(
634    FileId src_file_id, FileId dest_file_id) {
635  FileInfo src_file_info;
636  FileInfo dest_file_info;
637
638  if (!GetFileInfo(src_file_id, &src_file_info))
639    return false;
640  if (!GetFileInfo(dest_file_id, &dest_file_info))
641    return false;
642  if (src_file_info.is_directory() || dest_file_info.is_directory())
643    return false;
644  leveldb::WriteBatch batch;
645  // This is the only field that really gets moved over; if you add fields to
646  // FileInfo, e.g. ctime, they might need to be copied here.
647  dest_file_info.data_path = src_file_info.data_path;
648  if (!RemoveFileInfoHelper(src_file_id, &batch))
649    return false;
650  Pickle pickle;
651  if (!PickleFromFileInfo(dest_file_info, &pickle))
652    return false;
653  batch.Put(
654      GetFileLookupKey(dest_file_id),
655      leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
656                     pickle.size()));
657  leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
658  if (!status.ok()) {
659    HandleError(FROM_HERE, status);
660    return false;
661  }
662  return true;
663}
664
665bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
666  if (!Init(REPAIR_ON_CORRUPTION))
667    return false;
668  DCHECK(next);
669  std::string int_string;
670  leveldb::Status status =
671      db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
672  if (status.ok()) {
673    int64 temp;
674    if (!base::StringToInt64(int_string, &temp)) {
675      LOG(ERROR) << "Hit database corruption!";
676      return false;
677    }
678    ++temp;
679    status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
680        base::Int64ToString(temp));
681    if (!status.ok()) {
682      HandleError(FROM_HERE, status);
683      return false;
684    }
685    *next = temp;
686    return true;
687  }
688  if (!status.IsNotFound()) {
689    HandleError(FROM_HERE, status);
690    return false;
691  }
692  // The database must not yet exist; initialize it.
693  if (!StoreDefaultValues())
694    return false;
695
696  return GetNextInteger(next);
697}
698
699// static
700bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path) {
701  std::string name  = FilePathToString(path.Append(kDirectoryDatabaseName));
702  leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options());
703  if (status.ok())
704    return true;
705  LOG(WARNING) << "Failed to destroy a database with status " <<
706      status.ToString();
707  return false;
708}
709
710bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
711  if (db_)
712    return true;
713
714  std::string path =
715      FilePathToString(filesystem_data_directory_.Append(
716          kDirectoryDatabaseName));
717  leveldb::Options options;
718  options.max_open_files = 64;  // Use minimum.
719  options.create_if_missing = true;
720  leveldb::DB* db;
721  leveldb::Status status = leveldb::DB::Open(options, path, &db);
722  ReportInitStatus(status);
723  if (status.ok()) {
724    db_.reset(db);
725    return true;
726  }
727  HandleError(FROM_HERE, status);
728
729  // Corruption due to missing necessary MANIFEST-* file causes IOError instead
730  // of Corruption error.
731  // Try to repair database even when IOError case.
732  if (!status.IsCorruption() && !status.IsIOError())
733    return false;
734
735  switch (recovery_option) {
736    case FAIL_ON_CORRUPTION:
737      return false;
738    case REPAIR_ON_CORRUPTION:
739      LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
740                   << " Attempting to repair.";
741      if (RepairDatabase(path)) {
742        UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
743                                  DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
744        return true;
745      }
746      UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
747                                DB_REPAIR_FAILED, DB_REPAIR_MAX);
748      LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
749      // fall through
750    case DELETE_ON_CORRUPTION:
751      LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
752      if (!base::DeleteFile(filesystem_data_directory_, true))
753        return false;
754      if (!file_util::CreateDirectory(filesystem_data_directory_))
755        return false;
756      return Init(FAIL_ON_CORRUPTION);
757  }
758
759  NOTREACHED();
760  return false;
761}
762
763bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
764  DCHECK(!db_.get());
765  leveldb::Options options;
766  options.max_open_files = 64;  // Use minimum.
767  if (!leveldb::RepairDB(db_path, options).ok())
768    return false;
769  if (!Init(FAIL_ON_CORRUPTION))
770    return false;
771  if (IsFileSystemConsistent())
772    return true;
773  db_.reset();
774  return false;
775}
776
777bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
778  if (!Init(FAIL_ON_CORRUPTION))
779    return false;
780  DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
781  return helper.IsFileSystemConsistent();
782}
783
784void SandboxDirectoryDatabase::ReportInitStatus(
785    const leveldb::Status& status) {
786  base::Time now = base::Time::Now();
787  const base::TimeDelta minimum_interval =
788      base::TimeDelta::FromHours(kMinimumReportIntervalHours);
789  if (last_reported_time_ + minimum_interval >= now)
790    return;
791  last_reported_time_ = now;
792
793  if (status.ok()) {
794    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
795                              INIT_STATUS_OK, INIT_STATUS_MAX);
796  } else if (status.IsCorruption()) {
797    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
798                              INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
799  } else if (status.IsIOError()) {
800    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
801                              INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
802  } else {
803    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
804                              INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
805  }
806}
807
808bool SandboxDirectoryDatabase::StoreDefaultValues() {
809  // Verify that this is a totally new database, and initialize it.
810  scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
811  iter->SeekToFirst();
812  if (iter->Valid()) {  // DB was not empty--we shouldn't have been called.
813    LOG(ERROR) << "File system origin database is corrupt!";
814    return false;
815  }
816  // This is always the first write into the database.  If we ever add a
817  // version number, it should go in this transaction too.
818  FileInfo root;
819  root.parent_id = 0;
820  root.modification_time = base::Time::Now();
821  leveldb::WriteBatch batch;
822  if (!AddFileInfoHelper(root, 0, &batch))
823    return false;
824  batch.Put(LastFileIdKey(), base::Int64ToString(0));
825  batch.Put(LastIntegerKey(), base::Int64ToString(-1));
826  leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
827  if (!status.ok()) {
828    HandleError(FROM_HERE, status);
829    return false;
830  }
831  return true;
832}
833
834bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
835  if (!Init(REPAIR_ON_CORRUPTION))
836    return false;
837  DCHECK(file_id);
838  std::string id_string;
839  leveldb::Status status =
840      db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
841  if (status.ok()) {
842    if (!base::StringToInt64(id_string, file_id)) {
843      LOG(ERROR) << "Hit database corruption!";
844      return false;
845    }
846    return true;
847  }
848  if (!status.IsNotFound()) {
849    HandleError(FROM_HERE, status);
850    return false;
851  }
852  // The database must not yet exist; initialize it.
853  if (!StoreDefaultValues())
854    return false;
855  *file_id = 0;
856  return true;
857}
858
859bool SandboxDirectoryDatabase::VerifyIsDirectory(FileId file_id) {
860  FileInfo info;
861  if (!file_id)
862    return true;  // The root is a directory.
863  if (!GetFileInfo(file_id, &info))
864    return false;
865  if (!info.is_directory()) {
866    LOG(ERROR) << "New parent directory is a file!";
867    return false;
868  }
869  return true;
870}
871
872// This does very few safety checks!
873bool 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!
899bool 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
920void 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