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