Home | History | Annotate | Download | only in nacl_host
      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