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