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_origin_database.h"
6
7#include <set>
8#include <utility>
9
10#include "base/file_util.h"
11#include "base/files/file_enumerator.h"
12#include "base/format_macros.h"
13#include "base/location.h"
14#include "base/logging.h"
15#include "base/metrics/histogram.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/strings/string_util.h"
18#include "base/strings/stringprintf.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/common/fileapi/file_system_util.h"
22
23namespace {
24
25const base::FilePath::CharType kOriginDatabaseName[] =
26    FILE_PATH_LITERAL("Origins");
27const char kOriginKeyPrefix[] = "ORIGIN:";
28const char kLastPathKey[] = "LAST_PATH";
29const int64 kMinimumReportIntervalHours = 1;
30const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit";
31const char kDatabaseRepairHistogramLabel[] = "FileSystem.OriginDatabaseRepair";
32
33enum InitStatus {
34  INIT_STATUS_OK = 0,
35  INIT_STATUS_CORRUPTION,
36  INIT_STATUS_IO_ERROR,
37  INIT_STATUS_UNKNOWN_ERROR,
38  INIT_STATUS_MAX
39};
40
41enum RepairResult {
42  DB_REPAIR_SUCCEEDED = 0,
43  DB_REPAIR_FAILED,
44  DB_REPAIR_MAX
45};
46
47std::string OriginToOriginKey(const std::string& origin) {
48  std::string key(kOriginKeyPrefix);
49  return key + origin;
50}
51
52const char* LastPathKey() {
53  return kLastPathKey;
54}
55
56}  // namespace
57
58namespace fileapi {
59
60SandboxOriginDatabase::SandboxOriginDatabase(
61    const base::FilePath& file_system_directory)
62    : file_system_directory_(file_system_directory) {
63}
64
65SandboxOriginDatabase::~SandboxOriginDatabase() {
66}
67
68bool SandboxOriginDatabase::Init(InitOption init_option,
69                                 RecoveryOption recovery_option) {
70  if (db_)
71    return true;
72
73  base::FilePath db_path = GetDatabasePath();
74  if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path))
75    return false;
76
77  std::string path = FilePathToString(db_path);
78  leveldb::Options options;
79  options.max_open_files = 0;  // Use minimum.
80  options.create_if_missing = true;
81  leveldb::DB* db;
82  leveldb::Status status = leveldb::DB::Open(options, path, &db);
83  ReportInitStatus(status);
84  if (status.ok()) {
85    db_.reset(db);
86    return true;
87  }
88  HandleError(FROM_HERE, status);
89
90  // Corruption due to missing necessary MANIFEST-* file causes IOError instead
91  // of Corruption error.
92  // Try to repair database even when IOError case.
93  if (!status.IsCorruption() && !status.IsIOError())
94    return false;
95
96  switch (recovery_option) {
97    case FAIL_ON_CORRUPTION:
98      return false;
99    case REPAIR_ON_CORRUPTION:
100      LOG(WARNING) << "Attempting to repair SandboxOriginDatabase.";
101
102      if (RepairDatabase(path)) {
103        UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
104                                  DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
105        LOG(WARNING) << "Repairing SandboxOriginDatabase completed.";
106        return true;
107      }
108      UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
109                                DB_REPAIR_FAILED, DB_REPAIR_MAX);
110      // fall through
111    case DELETE_ON_CORRUPTION:
112      if (!base::DeleteFile(file_system_directory_, true))
113        return false;
114      if (!base::CreateDirectory(file_system_directory_))
115        return false;
116      return Init(init_option, FAIL_ON_CORRUPTION);
117  }
118  NOTREACHED();
119  return false;
120}
121
122bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) {
123  DCHECK(!db_.get());
124  leveldb::Options options;
125  options.max_open_files = 0;  // Use minimum.
126  if (!leveldb::RepairDB(db_path, options).ok() ||
127      !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) {
128    LOG(WARNING) << "Failed to repair SandboxOriginDatabase.";
129    return false;
130  }
131
132  // See if the repaired entries match with what we have on disk.
133  std::set<base::FilePath> directories;
134  base::FileEnumerator file_enum(file_system_directory_,
135                                 false /* recursive */,
136                                 base::FileEnumerator::DIRECTORIES);
137  base::FilePath path_each;
138  while (!(path_each = file_enum.Next()).empty())
139    directories.insert(path_each.BaseName());
140  std::set<base::FilePath>::iterator db_dir_itr =
141      directories.find(base::FilePath(kOriginDatabaseName));
142  // Make sure we have the database file in its directory and therefore we are
143  // working on the correct path.
144  DCHECK(db_dir_itr != directories.end());
145  directories.erase(db_dir_itr);
146
147  std::vector<OriginRecord> origins;
148  if (!ListAllOrigins(&origins)) {
149    DropDatabase();
150    return false;
151  }
152
153  // Delete any obsolete entries from the origins database.
154  for (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin();
155       db_origin_itr != origins.end();
156       ++db_origin_itr) {
157    std::set<base::FilePath>::iterator dir_itr =
158        directories.find(db_origin_itr->path);
159    if (dir_itr == directories.end()) {
160      if (!RemovePathForOrigin(db_origin_itr->origin)) {
161        DropDatabase();
162        return false;
163      }
164    } else {
165      directories.erase(dir_itr);
166    }
167  }
168
169  // Delete any directories not listed in the origins database.
170  for (std::set<base::FilePath>::iterator dir_itr = directories.begin();
171       dir_itr != directories.end();
172       ++dir_itr) {
173    if (!base::DeleteFile(file_system_directory_.Append(*dir_itr),
174                           true /* recursive */)) {
175      DropDatabase();
176      return false;
177    }
178  }
179
180  return true;
181}
182
183void SandboxOriginDatabase::HandleError(
184    const tracked_objects::Location& from_here,
185    const leveldb::Status& status) {
186  db_.reset();
187  LOG(ERROR) << "SandboxOriginDatabase failed at: "
188             << from_here.ToString() << " with error: " << status.ToString();
189}
190
191void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) {
192  base::Time now = base::Time::Now();
193  base::TimeDelta minimum_interval =
194      base::TimeDelta::FromHours(kMinimumReportIntervalHours);
195  if (last_reported_time_ + minimum_interval >= now)
196    return;
197  last_reported_time_ = now;
198
199  if (status.ok()) {
200    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
201                              INIT_STATUS_OK, INIT_STATUS_MAX);
202  } else if (status.IsCorruption()) {
203    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
204                              INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
205  } else if (status.IsIOError()) {
206    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
207                              INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
208  } else {
209    UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
210                              INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
211  }
212}
213
214bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) {
215  if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
216    return false;
217  if (origin.empty())
218    return false;
219  std::string path;
220  leveldb::Status status =
221      db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path);
222  if (status.ok())
223    return true;
224  if (status.IsNotFound())
225    return false;
226  HandleError(FROM_HERE, status);
227  return false;
228}
229
230bool SandboxOriginDatabase::GetPathForOrigin(
231    const std::string& origin, base::FilePath* directory) {
232  if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
233    return false;
234  DCHECK(directory);
235  if (origin.empty())
236    return false;
237  std::string path_string;
238  std::string origin_key = OriginToOriginKey(origin);
239  leveldb::Status status =
240      db_->Get(leveldb::ReadOptions(), origin_key, &path_string);
241  if (status.IsNotFound()) {
242    int last_path_number;
243    if (!GetLastPathNumber(&last_path_number))
244      return false;
245    path_string = base::StringPrintf("%03u", last_path_number + 1);
246    // store both back as a single transaction
247    leveldb::WriteBatch batch;
248    batch.Put(LastPathKey(), path_string);
249    batch.Put(origin_key, path_string);
250    status = db_->Write(leveldb::WriteOptions(), &batch);
251    if (!status.ok()) {
252      HandleError(FROM_HERE, status);
253      return false;
254    }
255  }
256  if (status.ok()) {
257    *directory = StringToFilePath(path_string);
258    return true;
259  }
260  HandleError(FROM_HERE, status);
261  return false;
262}
263
264bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) {
265  if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
266    return false;
267  leveldb::Status status =
268      db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin));
269  if (status.ok() || status.IsNotFound())
270    return true;
271  HandleError(FROM_HERE, status);
272  return false;
273}
274
275bool SandboxOriginDatabase::ListAllOrigins(
276    std::vector<OriginRecord>* origins) {
277  DCHECK(origins);
278  if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) {
279    origins->clear();
280    return false;
281  }
282  scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
283  std::string origin_key_prefix = OriginToOriginKey(std::string());
284  iter->Seek(origin_key_prefix);
285  origins->clear();
286  while (iter->Valid() &&
287      StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) {
288    std::string origin =
289      iter->key().ToString().substr(origin_key_prefix.length());
290    base::FilePath path = StringToFilePath(iter->value().ToString());
291    origins->push_back(OriginRecord(origin, path));
292    iter->Next();
293  }
294  return true;
295}
296
297void SandboxOriginDatabase::DropDatabase() {
298  db_.reset();
299}
300
301base::FilePath SandboxOriginDatabase::GetDatabasePath() const {
302  return file_system_directory_.Append(kOriginDatabaseName);
303}
304
305void SandboxOriginDatabase::RemoveDatabase() {
306  DropDatabase();
307  base::DeleteFile(GetDatabasePath(), true /* recursive */);
308}
309
310bool SandboxOriginDatabase::GetLastPathNumber(int* number) {
311  DCHECK(db_);
312  DCHECK(number);
313  std::string number_string;
314  leveldb::Status status =
315      db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string);
316  if (status.ok())
317    return base::StringToInt(number_string, number);
318  if (!status.IsNotFound()) {
319    HandleError(FROM_HERE, status);
320    return false;
321  }
322  // Verify that this is a totally new database, and initialize it.
323  scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
324  iter->SeekToFirst();
325  if (iter->Valid()) {  // DB was not empty, but had no last path number!
326    LOG(ERROR) << "File system origin database is corrupt!";
327    return false;
328  }
329  // This is always the first write into the database.  If we ever add a
330  // version number, they should go in in a single transaction.
331  status =
332      db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1"));
333  if (!status.ok()) {
334    HandleError(FROM_HERE, status);
335    return false;
336  }
337  *number = -1;
338  return true;
339}
340
341}  // namespace fileapi
342