1 // Copyright 2013 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 "chrome/browser/nacl_host/pnacl_translation_cache.h" 6 7 #include <string> 8 9 #include "base/files/file_path.h" 10 #include "base/logging.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/threading/thread_checker.h" 13 #include "components/nacl/common/pnacl_types.h" 14 #include "content/public/browser/browser_thread.h" 15 #include "net/base/io_buffer.h" 16 #include "net/base/net_errors.h" 17 #include "net/disk_cache/disk_cache.h" 18 19 using base::IntToString; 20 using content::BrowserThread; 21 22 namespace { 23 24 void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); } 25 26 } // namespace 27 28 namespace pnacl { 29 // This is in pnacl namespace instead of static so they can be used 30 // by the unit test. 31 const int kMaxMemCacheSize = 100 * 1024 * 1024; 32 33 ////////////////////////////////////////////////////////////////////// 34 // Handle Reading/Writing to Cache. 35 36 // PnaclTranslationCacheEntry is a shim that provides storage for the 37 // 'key' and 'data' strings as the disk_cache is performing various async 38 // operations. It also tracks the open disk_cache::Entry 39 // and ensures that the entry is closed. 40 class PnaclTranslationCacheEntry 41 : public base::RefCounted<PnaclTranslationCacheEntry> { 42 public: 43 static PnaclTranslationCacheEntry* GetReadEntry( 44 base::WeakPtr<PnaclTranslationCache> cache, 45 const std::string& key, 46 const GetNexeCallback& callback); 47 static PnaclTranslationCacheEntry* GetWriteEntry( 48 base::WeakPtr<PnaclTranslationCache> cache, 49 const std::string& key, 50 net::DrainableIOBuffer* write_nexe, 51 const CompletionCallback& callback); 52 53 void Start(); 54 55 // Writes: --- 56 // v | 57 // Start -> Open Existing --------------> Write ---> Close 58 // \ ^ 59 // \ / 60 // --> Create -- 61 // Reads: 62 // Start -> Open --------Read ----> Close 63 // | ^ 64 // |__| 65 enum CacheStep { 66 UNINITIALIZED, 67 OPEN_ENTRY, 68 CREATE_ENTRY, 69 TRANSFER_ENTRY, 70 CLOSE_ENTRY 71 }; 72 73 private: 74 friend class base::RefCounted<PnaclTranslationCacheEntry>; 75 PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache, 76 const std::string& key, 77 bool is_read); 78 ~PnaclTranslationCacheEntry(); 79 80 // Try to open an existing entry in the backend 81 void OpenEntry(); 82 // Create a new entry in the backend (for writes) 83 void CreateEntry(); 84 // Write |len| bytes to the backend, starting at |offset| 85 void WriteEntry(int offset, int len); 86 // Read |len| bytes from the backend, starting at |offset| 87 void ReadEntry(int offset, int len); 88 // If there was an error, doom the entry. Then post a task to the IO 89 // thread to close (and delete) it. 90 void CloseEntry(int rv); 91 // Call the user callback, and signal to the cache to delete this. 92 void Finish(int rv); 93 // Used as the callback for all operations to the backend. Handle state 94 // transitions, track bytes transferred, and call the other helper methods. 95 void DispatchNext(int rv); 96 97 base::WeakPtr<PnaclTranslationCache> cache_; 98 std::string key_; 99 disk_cache::Entry* entry_; 100 CacheStep step_; 101 bool is_read_; 102 GetNexeCallback read_callback_; 103 CompletionCallback write_callback_; 104 scoped_refptr<net::DrainableIOBuffer> io_buf_; 105 base::ThreadChecker thread_checker_; 106 DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry); 107 }; 108 109 // static 110 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry( 111 base::WeakPtr<PnaclTranslationCache> cache, 112 const std::string& key, 113 const GetNexeCallback& callback) { 114 PnaclTranslationCacheEntry* entry( 115 new PnaclTranslationCacheEntry(cache, key, true)); 116 entry->read_callback_ = callback; 117 return entry; 118 } 119 120 // static 121 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry( 122 base::WeakPtr<PnaclTranslationCache> cache, 123 const std::string& key, 124 net::DrainableIOBuffer* write_nexe, 125 const CompletionCallback& callback) { 126 PnaclTranslationCacheEntry* entry( 127 new PnaclTranslationCacheEntry(cache, key, false)); 128 entry->io_buf_ = write_nexe; 129 entry->write_callback_ = callback; 130 return entry; 131 } 132 133 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry( 134 base::WeakPtr<PnaclTranslationCache> cache, 135 const std::string& key, 136 bool is_read) 137 : cache_(cache), 138 key_(key), 139 entry_(NULL), 140 step_(UNINITIALIZED), 141 is_read_(is_read) {} 142 143 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() { 144 // Ensure we have called the user's callback 145 DCHECK(read_callback_.is_null()); 146 DCHECK(write_callback_.is_null()); 147 } 148 149 void PnaclTranslationCacheEntry::Start() { 150 DCHECK(thread_checker_.CalledOnValidThread()); 151 step_ = OPEN_ENTRY; 152 OpenEntry(); 153 } 154 155 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called 156 // from DispatchNext, so they know that cache_ is still valid. 157 void PnaclTranslationCacheEntry::OpenEntry() { 158 int rv = cache_->backend()->OpenEntry( 159 key_, 160 &entry_, 161 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); 162 if (rv != net::ERR_IO_PENDING) 163 DispatchNext(rv); 164 } 165 166 void PnaclTranslationCacheEntry::CreateEntry() { 167 int rv = cache_->backend()->CreateEntry( 168 key_, 169 &entry_, 170 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); 171 if (rv != net::ERR_IO_PENDING) 172 DispatchNext(rv); 173 } 174 175 void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) { 176 DCHECK(io_buf_->BytesRemaining() == len); 177 int rv = entry_->WriteData( 178 1, 179 offset, 180 io_buf_.get(), 181 len, 182 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this), 183 false); 184 if (rv != net::ERR_IO_PENDING) 185 DispatchNext(rv); 186 } 187 188 void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) { 189 int rv = entry_->ReadData( 190 1, 191 offset, 192 io_buf_.get(), 193 len, 194 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); 195 if (rv != net::ERR_IO_PENDING) 196 DispatchNext(rv); 197 } 198 199 void PnaclTranslationCacheEntry::CloseEntry(int rv) { 200 DCHECK(entry_); 201 if (rv < 0) { 202 LOG(ERROR) << "PnaclTranslationCache: failed to close entry: " 203 << net::ErrorToString(rv); 204 entry_->Doom(); 205 } 206 BrowserThread::PostTask( 207 BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_)); 208 Finish(rv); 209 } 210 211 void PnaclTranslationCacheEntry::Finish(int rv) { 212 if (is_read_) { 213 if (!read_callback_.is_null()) { 214 read_callback_.Run(rv, io_buf_); 215 read_callback_.Reset(); 216 } 217 } else { 218 if (!write_callback_.is_null()) { 219 write_callback_.Run(rv); 220 write_callback_.Reset(); 221 } 222 } 223 cache_->OpComplete(this); 224 } 225 226 void PnaclTranslationCacheEntry::DispatchNext(int rv) { 227 DCHECK(thread_checker_.CalledOnValidThread()); 228 if (!cache_) 229 return; 230 231 switch (step_) { 232 case UNINITIALIZED: 233 LOG(ERROR) << "PnaclTranslationCache: DispatchNext called uninitialized"; 234 break; 235 236 case OPEN_ENTRY: 237 if (rv == net::OK) { 238 step_ = TRANSFER_ENTRY; 239 if (is_read_) { 240 int bytes_to_transfer = entry_->GetDataSize(1); 241 io_buf_ = new net::DrainableIOBuffer( 242 new net::IOBuffer(bytes_to_transfer), bytes_to_transfer); 243 ReadEntry(0, bytes_to_transfer); 244 } else { 245 WriteEntry(0, io_buf_->size()); 246 } 247 } else { 248 if (rv != net::ERR_FAILED) { 249 // ERROR_FAILED is what we expect if the entry doesn't exist. 250 LOG(ERROR) << "PnaclTranslationCache: OpenEntry failed: " 251 << net::ErrorToString(rv); 252 } 253 if (is_read_) { 254 // Just a cache miss, not necessarily an error. 255 entry_ = NULL; 256 Finish(rv); 257 } else { 258 step_ = CREATE_ENTRY; 259 CreateEntry(); 260 } 261 } 262 break; 263 264 case CREATE_ENTRY: 265 if (rv == net::OK) { 266 step_ = TRANSFER_ENTRY; 267 WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); 268 } else { 269 LOG(ERROR) << "PnaclTranslationCache: Failed to Create Entry: " 270 << net::ErrorToString(rv); 271 Finish(rv); 272 } 273 break; 274 275 case TRANSFER_ENTRY: 276 if (rv < 0) { 277 // We do not call DispatchNext directly if WriteEntry/ReadEntry returns 278 // ERR_IO_PENDING, and the callback should not return that value either. 279 LOG(ERROR) 280 << "PnaclTranslationCache: Failed to complete write to entry: " 281 << net::ErrorToString(rv); 282 step_ = CLOSE_ENTRY; 283 CloseEntry(rv); 284 break; 285 } else if (rv > 0) { 286 io_buf_->DidConsume(rv); 287 if (io_buf_->BytesRemaining() > 0) { 288 is_read_ 289 ? ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()) 290 : WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); 291 break; 292 } 293 } 294 // rv == 0 or we fell through (i.e. we have transferred all the bytes) 295 step_ = CLOSE_ENTRY; 296 DCHECK(io_buf_->BytesConsumed() == io_buf_->size()); 297 if (is_read_) 298 io_buf_->SetOffset(0); 299 CloseEntry(0); 300 break; 301 302 case CLOSE_ENTRY: 303 step_ = UNINITIALIZED; 304 break; 305 } 306 } 307 308 ////////////////////////////////////////////////////////////////////// 309 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) { 310 open_entries_.erase(entry); 311 } 312 313 ////////////////////////////////////////////////////////////////////// 314 // Construction and cache backend initialization 315 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {} 316 317 PnaclTranslationCache::~PnaclTranslationCache() {} 318 319 int PnaclTranslationCache::InitWithDiskBackend( 320 const base::FilePath& cache_dir, 321 int cache_size, 322 const CompletionCallback& callback) { 323 return Init(net::DISK_CACHE, cache_dir, cache_size, callback); 324 } 325 326 int PnaclTranslationCache::InitWithMemBackend( 327 int cache_size, 328 const CompletionCallback& callback) { 329 return Init(net::MEMORY_CACHE, base::FilePath(), cache_size, callback); 330 } 331 332 int PnaclTranslationCache::Init(net::CacheType cache_type, 333 const base::FilePath& cache_dir, 334 int cache_size, 335 const CompletionCallback& callback) { 336 int rv = disk_cache::CreateCacheBackend( 337 cache_type, 338 net::CACHE_BACKEND_DEFAULT, 339 cache_dir, 340 cache_size, 341 true /* force_initialize */, 342 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), 343 NULL, /* dummy net log */ 344 &disk_cache_, 345 base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr())); 346 init_callback_ = callback; 347 if (rv != net::ERR_IO_PENDING) { 348 OnCreateBackendComplete(rv); 349 } 350 return rv; 351 } 352 353 void PnaclTranslationCache::OnCreateBackendComplete(int rv) { 354 if (rv < 0) { 355 LOG(ERROR) << "PnaclTranslationCache: backend init failed:" 356 << net::ErrorToString(rv); 357 } 358 // Invoke our client's callback function. 359 if (!init_callback_.is_null()) { 360 init_callback_.Run(rv); 361 init_callback_.Reset(); 362 } 363 } 364 365 ////////////////////////////////////////////////////////////////////// 366 // High-level API 367 368 void PnaclTranslationCache::StoreNexe(const std::string& key, 369 net::DrainableIOBuffer* nexe_data) { 370 StoreNexe(key, nexe_data, CompletionCallback()); 371 } 372 373 void PnaclTranslationCache::StoreNexe(const std::string& key, 374 net::DrainableIOBuffer* nexe_data, 375 const CompletionCallback& callback) { 376 PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry( 377 AsWeakPtr(), key, nexe_data, callback); 378 open_entries_[entry] = entry; 379 entry->Start(); 380 } 381 382 void PnaclTranslationCache::GetNexe(const std::string& key, 383 const GetNexeCallback& callback) { 384 PnaclTranslationCacheEntry* entry = 385 PnaclTranslationCacheEntry::GetReadEntry(AsWeakPtr(), key, callback); 386 open_entries_[entry] = entry; 387 entry->Start(); 388 } 389 390 int PnaclTranslationCache::InitCache(const base::FilePath& cache_directory, 391 bool in_memory, 392 const CompletionCallback& callback) { 393 int rv; 394 in_memory_ = in_memory; 395 if (in_memory_) { 396 rv = InitWithMemBackend(kMaxMemCacheSize, callback); 397 } else { 398 rv = InitWithDiskBackend(cache_directory, 0, callback); 399 } 400 401 return rv; 402 } 403 404 int PnaclTranslationCache::Size() { 405 if (!disk_cache_) 406 return -1; 407 return disk_cache_->GetEntryCount(); 408 } 409 410 // static 411 std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) { 412 if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0) 413 return std::string(); 414 std::string retval("ABI:"); 415 retval += IntToString(info.abi_version) + ";" + "opt:" + 416 IntToString(info.opt_level) + ";" + "URL:"; 417 // Filter the username, password, and ref components from the URL 418 GURL::Replacements replacements; 419 replacements.ClearUsername(); 420 replacements.ClearPassword(); 421 replacements.ClearRef(); 422 GURL key_url(info.pexe_url.ReplaceComponents(replacements)); 423 retval += key_url.spec() + ";"; 424 // You would think that there is already code to format base::Time values 425 // somewhere, but I haven't found it yet. In any case, doing it ourselves 426 // here means we can keep the format stable. 427 base::Time::Exploded exploded; 428 info.last_modified.UTCExplode(&exploded); 429 if (info.last_modified.is_null() || !exploded.HasValidValues()) { 430 memset(&exploded, 0, sizeof(exploded)); 431 } 432 retval += "modified:" + IntToString(exploded.year) + ":" + 433 IntToString(exploded.month) + ":" + 434 IntToString(exploded.day_of_month) + ":" + 435 IntToString(exploded.hour) + ":" + IntToString(exploded.minute) + 436 ":" + IntToString(exploded.second) + ":" + 437 IntToString(exploded.millisecond) + ":UTC;"; 438 retval += "etag:" + info.etag; 439 return retval; 440 } 441 442 int PnaclTranslationCache::DoomEntriesBetween( 443 base::Time initial, 444 base::Time end, 445 const CompletionCallback& callback) { 446 return disk_cache_->DoomEntriesBetween(initial, end, callback); 447 } 448 449 } // namespace pnacl 450