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