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
20using base::IntToString;
21using content::BrowserThread;
22
23namespace {
24
25void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); }
26
27}  // namespace
28
29namespace pnacl {
30// This is in pnacl namespace instead of static so they can be used
31// by the unit test.
32const 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.
41class 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
112PnaclTranslationCacheEntry* 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
123PnaclTranslationCacheEntry* 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
135PnaclTranslationCacheEntry::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
145PnaclTranslationCacheEntry::~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
164void 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.
172void 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
181void 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
190void 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
203void 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
214void 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
225void 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
242void 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//////////////////////////////////////////////////////////////////////
323void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
324  open_entries_.erase(entry);
325}
326
327//////////////////////////////////////////////////////////////////////
328// Construction and cache backend initialization
329PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
330
331PnaclTranslationCache::~PnaclTranslationCache() {}
332
333int 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
353void 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
367void 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
376void 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
384int 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
390int PnaclTranslationCache::InitInMemory(const CompletionCallback& callback) {
391  in_memory_ = true;
392  return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, callback);
393}
394
395int PnaclTranslationCache::Size() {
396  if (!disk_cache_)
397    return -1;
398  return disk_cache_->GetEntryCount();
399}
400
401// Beware that any changes to this function or to PnaclCacheInfo will
402// effectively invalidate existing translation cache entries.
403
404// static
405std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) {
406  if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0 ||
407      info.extra_flags.size() > 512)
408    return std::string();
409  std::string retval("ABI:");
410  retval += IntToString(info.abi_version) + ";" + "opt:" +
411            IntToString(info.opt_level) + ";" + "URL:";
412  // Filter the username, password, and ref components from the URL
413  GURL::Replacements replacements;
414  replacements.ClearUsername();
415  replacements.ClearPassword();
416  replacements.ClearRef();
417  GURL key_url(info.pexe_url.ReplaceComponents(replacements));
418  retval += key_url.spec() + ";";
419  // You would think that there is already code to format base::Time values
420  // somewhere, but I haven't found it yet. In any case, doing it ourselves
421  // here means we can keep the format stable.
422  base::Time::Exploded exploded;
423  info.last_modified.UTCExplode(&exploded);
424  if (info.last_modified.is_null() || !exploded.HasValidValues()) {
425    memset(&exploded, 0, sizeof(exploded));
426  }
427  retval += "modified:" + IntToString(exploded.year) + ":" +
428            IntToString(exploded.month) + ":" +
429            IntToString(exploded.day_of_month) + ":" +
430            IntToString(exploded.hour) + ":" + IntToString(exploded.minute) +
431            ":" + IntToString(exploded.second) + ":" +
432            IntToString(exploded.millisecond) + ":UTC;";
433  retval += "etag:" + info.etag + ";";
434  retval += "sandbox:" + info.sandbox_isa + ";";
435  retval += "extra_flags:" + info.extra_flags + ";";
436  return retval;
437}
438
439int PnaclTranslationCache::DoomEntriesBetween(
440    base::Time initial,
441    base::Time end,
442    const CompletionCallback& callback) {
443  return disk_cache_->DoomEntriesBetween(initial, end, callback);
444}
445
446}  // namespace pnacl
447