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 leveldb::Env* env_override) 63 : file_system_directory_(file_system_directory), 64 env_override_(env_override) { 65 } 66 67 SandboxOriginDatabase::~SandboxOriginDatabase() { 68 } 69 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 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 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 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 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 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 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 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 303 void SandboxOriginDatabase::DropDatabase() { 304 db_.reset(); 305 } 306 307 base::FilePath SandboxOriginDatabase::GetDatabasePath() const { 308 return file_system_directory_.Append(kOriginDatabaseName); 309 } 310 311 void SandboxOriginDatabase::RemoveDatabase() { 312 DropDatabase(); 313 base::DeleteFile(GetDatabasePath(), true /* recursive */); 314 } 315 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