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