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
19using base::IntToString;
20using content::BrowserThread;
21
22namespace {
23
24void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); }
25
26}  // namespace
27
28namespace pnacl {
29// This is in pnacl namespace instead of static so they can be used
30// by the unit test.
31const 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.
40class 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
110PnaclTranslationCacheEntry* 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
121PnaclTranslationCacheEntry* 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
133PnaclTranslationCacheEntry::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
143PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
144  // Ensure we have called the user's callback
145  DCHECK(read_callback_.is_null());
146  DCHECK(write_callback_.is_null());
147}
148
149void 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.
157void 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
166void 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
175void 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
188void 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
199void 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
211void 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
226void 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//////////////////////////////////////////////////////////////////////
309void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
310  open_entries_.erase(entry);
311}
312
313//////////////////////////////////////////////////////////////////////
314// Construction and cache backend initialization
315PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
316
317PnaclTranslationCache::~PnaclTranslationCache() {}
318
319int 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
326int PnaclTranslationCache::InitWithMemBackend(
327    int cache_size,
328    const CompletionCallback& callback) {
329  return Init(net::MEMORY_CACHE, base::FilePath(), cache_size, callback);
330}
331
332int 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
353void 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
368void PnaclTranslationCache::StoreNexe(const std::string& key,
369                                      net::DrainableIOBuffer* nexe_data) {
370  StoreNexe(key, nexe_data, CompletionCallback());
371}
372
373void 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
382void 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
390int 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
404int PnaclTranslationCache::Size() {
405  if (!disk_cache_)
406    return -1;
407  return disk_cache_->GetEntryCount();
408}
409
410// static
411std::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
442int 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