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 47 std::string OriginToOriginKey(const std::string& origin) { 48 std::string key(kOriginKeyPrefix); 49 return key + origin; 50 } 51 52 const char* LastPathKey() { 53 return kLastPathKey; 54 } 55 56 } // namespace 57 58 namespace fileapi { 59 60 SandboxOriginDatabase::SandboxOriginDatabase( 61 const base::FilePath& file_system_directory) 62 : file_system_directory_(file_system_directory) { 63 } 64 65 SandboxOriginDatabase::~SandboxOriginDatabase() { 66 } 67 68 bool 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 122 bool 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 183 void 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 191 void 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 214 bool 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 230 bool 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 264 bool 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 275 bool 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 297 void SandboxOriginDatabase::DropDatabase() { 298 db_.reset(); 299 } 300 301 base::FilePath SandboxOriginDatabase::GetDatabasePath() const { 302 return file_system_directory_.Append(kOriginDatabaseName); 303 } 304 305 void SandboxOriginDatabase::RemoveDatabase() { 306 DropDatabase(); 307 base::DeleteFile(GetDatabasePath(), true /* recursive */); 308 } 309 310 bool 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