Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / storage / browser / fileapi / sandbox_directory_database.cc
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
24 namespace {
25
26 bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info,
27                         Pickle* pickle) {
28   DCHECK(pickle);
29   std::string data_path;
30   // Round off here to match the behavior of the filesystem on real files.
31   base::Time time =
32       base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
33   std::string name;
34
35   data_path = storage::FilePathToString(info.data_path);
36   name = storage::FilePathToString(base::FilePath(info.name));
37
38   if (pickle->WriteInt64(info.parent_id) &&
39       pickle->WriteString(data_path) &&
40       pickle->WriteString(name) &&
41       pickle->WriteInt64(time.ToInternalValue()))
42     return true;
43
44   NOTREACHED();
45   return false;
46 }
47
48 bool FileInfoFromPickle(const Pickle& pickle,
49                         storage::SandboxDirectoryDatabase::FileInfo* info) {
50   PickleIterator iter(pickle);
51   std::string data_path;
52   std::string name;
53   int64 internal_time;
54
55   if (pickle.ReadInt64(&iter, &info->parent_id) &&
56       pickle.ReadString(&iter, &data_path) &&
57       pickle.ReadString(&iter, &name) &&
58       pickle.ReadInt64(&iter, &internal_time)) {
59     info->data_path = storage::StringToFilePath(data_path);
60     info->name = storage::StringToFilePath(name).value();
61     info->modification_time = base::Time::FromInternalValue(internal_time);
62     return true;
63   }
64   LOG(ERROR) << "Pickle could not be digested!";
65   return false;
66 }
67
68 const base::FilePath::CharType kDirectoryDatabaseName[] =
69     FILE_PATH_LITERAL("Paths");
70 const char kChildLookupPrefix[] = "CHILD_OF:";
71 const char kChildLookupSeparator[] = ":";
72 const char kLastFileIdKey[] = "LAST_FILE_ID";
73 const char kLastIntegerKey[] = "LAST_INTEGER";
74 const int64 kMinimumReportIntervalHours = 1;
75 const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
76 const char kDatabaseRepairHistogramLabel[] =
77     "FileSystem.DirectoryDatabaseRepair";
78
79 enum InitStatus {
80   INIT_STATUS_OK = 0,
81   INIT_STATUS_CORRUPTION,
82   INIT_STATUS_IO_ERROR,
83   INIT_STATUS_UNKNOWN_ERROR,
84   INIT_STATUS_MAX
85 };
86
87 enum RepairResult {
88   DB_REPAIR_SUCCEEDED = 0,
89   DB_REPAIR_FAILED,
90   DB_REPAIR_MAX
91 };
92
93 std::string GetChildLookupKey(
94     storage::SandboxDirectoryDatabase::FileId parent_id,
95     const base::FilePath::StringType& child_name) {
96   std::string name;
97   name = storage::FilePathToString(base::FilePath(child_name));
98   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
99       std::string(kChildLookupSeparator) + name;
100 }
101
102 std::string GetChildListingKeyPrefix(
103     storage::SandboxDirectoryDatabase::FileId parent_id) {
104   return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
105       std::string(kChildLookupSeparator);
106 }
107
108 const char* LastFileIdKey() {
109   return kLastFileIdKey;
110 }
111
112 const char* LastIntegerKey() {
113   return kLastIntegerKey;
114 }
115
116 std::string GetFileLookupKey(
117     storage::SandboxDirectoryDatabase::FileId file_id) {
118   return base::Int64ToString(file_id);
119 }
120
121 // Assumptions:
122 //  - Any database entry is one of:
123 //    - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
124 //    - ("LAST_FILE_ID", "|last_file_id|"),
125 //    - ("LAST_INTEGER", "|last_integer|"),
126 //    - ("|file_id|", "pickled FileInfo")
127 //        where FileInfo has |parent_id|, |data_path|, |name| and
128 //        |modification_time|,
129 // Constraints:
130 //  - Each file in the database has unique backing file.
131 //  - Each file in |filesystem_data_directory_| has a database entry.
132 //  - Directory structure is tree, i.e. connected and acyclic.
133 class DatabaseCheckHelper {
134  public:
135   typedef storage::SandboxDirectoryDatabase::FileId FileId;
136   typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
137
138   DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db,
139                       leveldb::DB* db,
140                       const base::FilePath& path);
141
142   bool IsFileSystemConsistent() {
143     return IsDatabaseEmpty() ||
144         (ScanDatabase() && ScanDirectory() && ScanHierarchy());
145   }
146
147  private:
148   bool IsDatabaseEmpty();
149   // These 3 methods need to be called in the order.  Each method requires its
150   // previous method finished successfully. They also require the database is
151   // not empty.
152   bool ScanDatabase();
153   bool ScanDirectory();
154   bool ScanHierarchy();
155
156   storage::SandboxDirectoryDatabase* dir_db_;
157   leveldb::DB* db_;
158   base::FilePath path_;
159
160   std::set<base::FilePath> files_in_db_;
161
162   size_t num_directories_in_db_;
163   size_t num_files_in_db_;
164   size_t num_hierarchy_links_in_db_;
165
166   FileId last_file_id_;
167   FileId last_integer_;
168 };
169
170 DatabaseCheckHelper::DatabaseCheckHelper(
171     storage::SandboxDirectoryDatabase* dir_db,
172     leveldb::DB* db,
173     const base::FilePath& path)
174     : dir_db_(dir_db),
175       db_(db),
176       path_(path),
177       num_directories_in_db_(0),
178       num_files_in_db_(0),
179       num_hierarchy_links_in_db_(0),
180       last_file_id_(-1),
181       last_integer_(-1) {
182   DCHECK(dir_db_);
183   DCHECK(db_);
184   DCHECK(!path_.empty() && base::DirectoryExists(path_));
185 }
186
187 bool DatabaseCheckHelper::IsDatabaseEmpty() {
188   scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
189   itr->SeekToFirst();
190   return !itr->Valid();
191 }
192
193 bool DatabaseCheckHelper::ScanDatabase() {
194   // Scans all database entries sequentially to verify each of them has unique
195   // backing file.
196   int64 max_file_id = -1;
197   std::set<FileId> file_ids;
198
199   scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
200   for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
201     std::string key = itr->key().ToString();
202     if (StartsWithASCII(key, kChildLookupPrefix, true)) {
203       // key: "CHILD_OF:<parent_id>:<name>"
204       // value: "<child_id>"
205       ++num_hierarchy_links_in_db_;
206     } else if (key == kLastFileIdKey) {
207       // key: "LAST_FILE_ID"
208       // value: "<last_file_id>"
209       if (last_file_id_ >= 0 ||
210           !base::StringToInt64(itr->value().ToString(), &last_file_id_))
211         return false;
212
213       if (last_file_id_ < 0)
214         return false;
215     } else if (key == kLastIntegerKey) {
216       // key: "LAST_INTEGER"
217       // value: "<last_integer>"
218       if (last_integer_ >= 0 ||
219           !base::StringToInt64(itr->value().ToString(), &last_integer_))
220         return false;
221     } else {
222       // key: "<entry_id>"
223       // value: "<pickled FileInfo>"
224       FileInfo file_info;
225       if (!FileInfoFromPickle(
226               Pickle(itr->value().data(), itr->value().size()), &file_info))
227         return false;
228
229       FileId file_id = -1;
230       if (!base::StringToInt64(key, &file_id) || file_id < 0)
231         return false;
232
233       if (max_file_id < file_id)
234         max_file_id = file_id;
235       if (!file_ids.insert(file_id).second)
236         return false;
237
238       if (file_info.is_directory()) {
239         ++num_directories_in_db_;
240         DCHECK(file_info.data_path.empty());
241       } else {
242         // Ensure any pair of file entry don't share their data_path.
243         if (!files_in_db_.insert(file_info.data_path).second)
244           return false;
245
246         // Ensure the backing file exists as a normal file.
247         base::File::Info platform_file_info;
248         if (!base::GetFileInfo(
249                 path_.Append(file_info.data_path), &platform_file_info) ||
250             platform_file_info.is_directory ||
251             platform_file_info.is_symbolic_link) {
252           // leveldb::Iterator iterates a snapshot of the database.
253           // So even after RemoveFileInfo() call, we'll visit hierarchy link
254           // from |parent_id| to |file_id|.
255           if (!dir_db_->RemoveFileInfo(file_id))
256             return false;
257           --num_hierarchy_links_in_db_;
258           files_in_db_.erase(file_info.data_path);
259         } else {
260           ++num_files_in_db_;
261         }
262       }
263     }
264   }
265
266   // TODO(tzik): Add constraint for |last_integer_| to avoid possible
267   // data path confliction on ObfuscatedFileUtil.
268   return max_file_id <= last_file_id_;
269 }
270
271 bool DatabaseCheckHelper::ScanDirectory() {
272   // TODO(kinuko): Scans all local file system entries to verify each of them
273   // has a database entry.
274   const base::FilePath kExcludes[] = {
275       base::FilePath(kDirectoryDatabaseName),
276       base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
277   };
278
279   // Any path in |pending_directories| is relative to |path_|.
280   std::stack<base::FilePath> pending_directories;
281   pending_directories.push(base::FilePath());
282
283   while (!pending_directories.empty()) {
284     base::FilePath dir_path = pending_directories.top();
285     pending_directories.pop();
286
287     base::FileEnumerator file_enum(
288         dir_path.empty() ? path_ : path_.Append(dir_path),
289         false /* not recursive */,
290         base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
291
292     base::FilePath absolute_file_path;
293     while (!(absolute_file_path = file_enum.Next()).empty()) {
294       base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
295
296       base::FilePath relative_file_path;
297       if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
298         return false;
299
300       if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
301                     relative_file_path) != kExcludes + arraysize(kExcludes))
302         continue;
303
304       if (find_info.IsDirectory()) {
305         pending_directories.push(relative_file_path);
306         continue;
307       }
308
309       // Check if the file has a database entry.
310       std::set<base::FilePath>::iterator itr =
311           files_in_db_.find(relative_file_path);
312       if (itr == files_in_db_.end()) {
313         if (!base::DeleteFile(absolute_file_path, false))
314           return false;
315       } else {
316         files_in_db_.erase(itr);
317       }
318     }
319   }
320
321   return files_in_db_.empty();
322 }
323
324 bool DatabaseCheckHelper::ScanHierarchy() {
325   size_t visited_directories = 0;
326   size_t visited_files = 0;
327   size_t visited_links = 0;
328
329   std::stack<FileId> directories;
330   directories.push(0);
331
332   // Check if the root directory exists as a directory.
333   FileInfo file_info;
334   if (!dir_db_->GetFileInfo(0, &file_info))
335     return false;
336   if (file_info.parent_id != 0 ||
337       !file_info.is_directory())
338     return false;
339
340   while (!directories.empty()) {
341     ++visited_directories;
342     FileId dir_id = directories.top();
343     directories.pop();
344
345     std::vector<FileId> children;
346     if (!dir_db_->ListChildren(dir_id, &children))
347       return false;
348     for (std::vector<FileId>::iterator itr = children.begin();
349          itr != children.end();
350          ++itr) {
351       // Any directory must not have root directory as child.
352       if (!*itr)
353         return false;
354
355       // Check if the child knows the parent as its parent.
356       FileInfo file_info;
357       if (!dir_db_->GetFileInfo(*itr, &file_info))
358         return false;
359       if (file_info.parent_id != dir_id)
360         return false;
361
362       // Check if the parent knows the name of its child correctly.
363       FileId file_id;
364       if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
365           file_id != *itr)
366         return false;
367
368       if (file_info.is_directory())
369         directories.push(*itr);
370       else
371         ++visited_files;
372       ++visited_links;
373     }
374   }
375
376   // Check if we've visited all database entries.
377   return num_directories_in_db_ == visited_directories &&
378       num_files_in_db_ == visited_files &&
379       num_hierarchy_links_in_db_ == visited_links;
380 }
381
382 // Returns true if the given |data_path| contains no parent references ("..")
383 // and does not refer to special system files.
384 // This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
385 // ensure we're only dealing with valid data paths.
386 bool VerifyDataPath(const base::FilePath& data_path) {
387   // |data_path| should not contain any ".." and should be a relative path
388   // (to the filesystem_data_directory_).
389   if (data_path.ReferencesParent() || data_path.IsAbsolute())
390     return false;
391   // See if it's not pointing to the special system paths.
392   const base::FilePath kExcludes[] = {
393       base::FilePath(kDirectoryDatabaseName),
394       base::FilePath(storage::FileSystemUsageCache::kUsageFileName),
395   };
396   for (size_t i = 0; i < arraysize(kExcludes); ++i) {
397     if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
398       return false;
399   }
400   return true;
401 }
402
403 }  // namespace
404
405 namespace storage {
406
407 SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
408 }
409
410 SandboxDirectoryDatabase::FileInfo::~FileInfo() {
411 }
412
413 SandboxDirectoryDatabase::SandboxDirectoryDatabase(
414     const base::FilePath& filesystem_data_directory,
415     leveldb::Env* env_override)
416     : filesystem_data_directory_(filesystem_data_directory),
417       env_override_(env_override) {
418 }
419
420 SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
421 }
422
423 bool SandboxDirectoryDatabase::GetChildWithName(
424     FileId parent_id,
425     const base::FilePath::StringType& name,
426     FileId* child_id) {
427   if (!Init(REPAIR_ON_CORRUPTION))
428     return false;
429   DCHECK(child_id);
430   std::string child_key = GetChildLookupKey(parent_id, name);
431   std::string child_id_string;
432   leveldb::Status status =
433       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
434   if (status.IsNotFound())
435     return false;
436   if (status.ok()) {
437     if (!base::StringToInt64(child_id_string, child_id)) {
438       LOG(ERROR) << "Hit database corruption!";
439       return false;
440     }
441     return true;
442   }
443   HandleError(FROM_HERE, status);
444   return false;
445 }
446
447 bool SandboxDirectoryDatabase::GetFileWithPath(
448     const base::FilePath& path, FileId* file_id) {
449   std::vector<base::FilePath::StringType> components;
450   VirtualPath::GetComponents(path, &components);
451   FileId local_id = 0;
452   std::vector<base::FilePath::StringType>::iterator iter;
453   for (iter = components.begin(); iter != components.end(); ++iter) {
454     base::FilePath::StringType name;
455     name = *iter;
456     if (name == FILE_PATH_LITERAL("/"))
457       continue;
458     if (!GetChildWithName(local_id, name, &local_id))
459       return false;
460   }
461   *file_id = local_id;
462   return true;
463 }
464
465 bool SandboxDirectoryDatabase::ListChildren(
466     FileId parent_id, std::vector<FileId>* children) {
467   // Check to add later: fail if parent is a file, at least in debug builds.
468   if (!Init(REPAIR_ON_CORRUPTION))
469     return false;
470   DCHECK(children);
471   std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
472
473   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
474   iter->Seek(child_key_prefix);
475   children->clear();
476   while (iter->Valid() &&
477       StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
478     std::string child_id_string = iter->value().ToString();
479     FileId child_id;
480     if (!base::StringToInt64(child_id_string, &child_id)) {
481       LOG(ERROR) << "Hit database corruption!";
482       return false;
483     }
484     children->push_back(child_id);
485     iter->Next();
486   }
487   return true;
488 }
489
490 bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
491   if (!Init(REPAIR_ON_CORRUPTION))
492     return false;
493   DCHECK(info);
494   std::string file_key = GetFileLookupKey(file_id);
495   std::string file_data_string;
496   leveldb::Status status =
497       db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
498   if (status.ok()) {
499     bool success = FileInfoFromPickle(
500         Pickle(file_data_string.data(), file_data_string.length()), info);
501     if (!success)
502       return false;
503     if (!VerifyDataPath(info->data_path)) {
504       LOG(ERROR) << "Resolved data path is invalid: "
505                  << info->data_path.value();
506       return false;
507     }
508     return true;
509   }
510   // Special-case the root, for databases that haven't been initialized yet.
511   // Without this, a query for the root's file info, made before creating the
512   // first file in the database, will fail and confuse callers.
513   if (status.IsNotFound() && !file_id) {
514     info->name = base::FilePath::StringType();
515     info->data_path = base::FilePath();
516     info->modification_time = base::Time::Now();
517     info->parent_id = 0;
518     return true;
519   }
520   HandleError(FROM_HERE, status);
521   return false;
522 }
523
524 base::File::Error SandboxDirectoryDatabase::AddFileInfo(
525     const FileInfo& info, FileId* file_id) {
526   if (!Init(REPAIR_ON_CORRUPTION))
527     return base::File::FILE_ERROR_FAILED;
528   DCHECK(file_id);
529   std::string child_key = GetChildLookupKey(info.parent_id, info.name);
530   std::string child_id_string;
531   leveldb::Status status =
532       db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
533   if (status.ok()) {
534     LOG(ERROR) << "File exists already!";
535     return base::File::FILE_ERROR_EXISTS;
536   }
537   if (!status.IsNotFound()) {
538     HandleError(FROM_HERE, status);
539     return base::File::FILE_ERROR_NOT_FOUND;
540   }
541
542   if (!IsDirectory(info.parent_id)) {
543     LOG(ERROR) << "New parent directory is a file!";
544     return base::File::FILE_ERROR_NOT_A_DIRECTORY;
545   }
546
547   // This would be a fine place to limit the number of files in a directory, if
548   // we decide to add that restriction.
549
550   FileId temp_id;
551   if (!GetLastFileId(&temp_id))
552     return base::File::FILE_ERROR_FAILED;
553   ++temp_id;
554
555   leveldb::WriteBatch batch;
556   if (!AddFileInfoHelper(info, temp_id, &batch))
557     return base::File::FILE_ERROR_FAILED;
558
559   batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
560   status = db_->Write(leveldb::WriteOptions(), &batch);
561   if (!status.ok()) {
562     HandleError(FROM_HERE, status);
563     return base::File::FILE_ERROR_FAILED;
564   }
565   *file_id = temp_id;
566   return base::File::FILE_OK;
567 }
568
569 bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
570   if (!Init(REPAIR_ON_CORRUPTION))
571     return false;
572   leveldb::WriteBatch batch;
573   if (!RemoveFileInfoHelper(file_id, &batch))
574     return false;
575   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
576   if (!status.ok()) {
577     HandleError(FROM_HERE, status);
578     return false;
579   }
580   return true;
581 }
582
583 bool SandboxDirectoryDatabase::UpdateFileInfo(
584     FileId file_id, const FileInfo& new_info) {
585   // TODO(ericu): We should also check to see that this doesn't create a loop,
586   // but perhaps only in a debug build.
587   if (!Init(REPAIR_ON_CORRUPTION))
588     return false;
589   DCHECK(file_id);  // You can't remove the root, ever.  Just delete the DB.
590   FileInfo old_info;
591   if (!GetFileInfo(file_id, &old_info))
592     return false;
593   if (old_info.parent_id != new_info.parent_id &&
594       !IsDirectory(new_info.parent_id))
595     return false;
596   if (old_info.parent_id != new_info.parent_id ||
597       old_info.name != new_info.name) {
598     // Check for name clashes.
599     FileId temp_id;
600     if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
601       LOG(ERROR) << "Name collision on move.";
602       return false;
603     }
604   }
605   leveldb::WriteBatch batch;
606   if (!RemoveFileInfoHelper(file_id, &batch) ||
607       !AddFileInfoHelper(new_info, file_id, &batch))
608     return false;
609   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
610   if (!status.ok()) {
611     HandleError(FROM_HERE, status);
612     return false;
613   }
614   return true;
615 }
616
617 bool SandboxDirectoryDatabase::UpdateModificationTime(
618     FileId file_id, const base::Time& modification_time) {
619   FileInfo info;
620   if (!GetFileInfo(file_id, &info))
621     return false;
622   info.modification_time = modification_time;
623   Pickle pickle;
624   if (!PickleFromFileInfo(info, &pickle))
625     return false;
626   leveldb::Status status = db_->Put(
627       leveldb::WriteOptions(),
628       GetFileLookupKey(file_id),
629       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
630                      pickle.size()));
631   if (!status.ok()) {
632     HandleError(FROM_HERE, status);
633     return false;
634   }
635   return true;
636 }
637
638 bool SandboxDirectoryDatabase::OverwritingMoveFile(
639     FileId src_file_id, FileId dest_file_id) {
640   FileInfo src_file_info;
641   FileInfo dest_file_info;
642
643   if (!GetFileInfo(src_file_id, &src_file_info))
644     return false;
645   if (!GetFileInfo(dest_file_id, &dest_file_info))
646     return false;
647   if (src_file_info.is_directory() || dest_file_info.is_directory())
648     return false;
649   leveldb::WriteBatch batch;
650   // This is the only field that really gets moved over; if you add fields to
651   // FileInfo, e.g. ctime, they might need to be copied here.
652   dest_file_info.data_path = src_file_info.data_path;
653   if (!RemoveFileInfoHelper(src_file_id, &batch))
654     return false;
655   Pickle pickle;
656   if (!PickleFromFileInfo(dest_file_info, &pickle))
657     return false;
658   batch.Put(
659       GetFileLookupKey(dest_file_id),
660       leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
661                      pickle.size()));
662   leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
663   if (!status.ok()) {
664     HandleError(FROM_HERE, status);
665     return false;
666   }
667   return true;
668 }
669
670 bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
671   if (!Init(REPAIR_ON_CORRUPTION))
672     return false;
673   DCHECK(next);
674   std::string int_string;
675   leveldb::Status status =
676       db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
677   if (status.ok()) {
678     int64 temp;
679     if (!base::StringToInt64(int_string, &temp)) {
680       LOG(ERROR) << "Hit database corruption!";
681       return false;
682     }
683     ++temp;
684     status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
685         base::Int64ToString(temp));
686     if (!status.ok()) {
687       HandleError(FROM_HERE, status);
688       return false;
689     }
690     *next = temp;
691     return true;
692   }
693   if (!status.IsNotFound()) {
694     HandleError(FROM_HERE, status);
695     return false;
696   }
697   // The database must not yet exist; initialize it.
698   if (!StoreDefaultValues())
699     return false;
700
701   return GetNextInteger(next);
702 }
703
704 bool 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
720 bool 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
775 bool 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
791 bool 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
802 bool 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
809 void 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
833 bool 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
859 bool 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!
885 bool 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!
911 bool 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
932 void 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