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.
5 #include "components/nacl/browser/pnacl_translation_cache.h"
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"
20 using base::IntToString
;
21 using content::BrowserThread
;
25 void CloseDiskCacheEntry(disk_cache::Entry
* entry
) { entry
->Close(); }
30 // This is in pnacl namespace instead of static so they can be used
32 const int kMaxMemCacheSize
= 100 * 1024 * 1024;
34 //////////////////////////////////////////////////////////////////////
35 // Handle Reading/Writing to Cache.
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
> {
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
);
58 // Start -> Open Existing --------------> Write ---> Close
63 // Start -> Open --------Read ----> Close
76 friend class base::RefCounted
<PnaclTranslationCacheEntry
>;
77 PnaclTranslationCacheEntry(base::WeakPtr
<PnaclTranslationCache
> cache
,
78 const std::string
& key
,
80 ~PnaclTranslationCacheEntry();
82 // Try to open an existing entry in the backend
84 // Create a new entry in the backend (for writes)
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.
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
);
99 base::WeakPtr
<PnaclTranslationCache
> cache_
;
101 disk_cache::Entry
* entry_
;
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
);
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
;
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
;
135 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
136 base::WeakPtr
<PnaclTranslationCache
> cache
,
137 const std::string
& key
,
142 step_(UNINITIALIZED
),
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(
152 base::Bind(read_callback_
,
154 scoped_refptr
<net::DrainableIOBuffer
>()));
156 if (!write_callback_
.is_null()) {
157 BrowserThread::PostTask(BrowserThread::IO
,
159 base::Bind(write_callback_
, net::ERR_ABORTED
));
164 void PnaclTranslationCacheEntry::Start() {
165 DCHECK(thread_checker_
.CalledOnValidThread());
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(
176 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
177 if (rv
!= net::ERR_IO_PENDING
)
181 void PnaclTranslationCacheEntry::CreateEntry() {
182 int rv
= cache_
->backend()->CreateEntry(
185 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
186 if (rv
!= net::ERR_IO_PENDING
)
190 void PnaclTranslationCacheEntry::WriteEntry(int offset
, int len
) {
191 DCHECK(io_buf_
->BytesRemaining() == len
);
192 int rv
= entry_
->WriteData(
197 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this),
199 if (rv
!= net::ERR_IO_PENDING
)
203 void PnaclTranslationCacheEntry::ReadEntry(int offset
, int len
) {
204 int rv
= entry_
->ReadData(
209 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
210 if (rv
!= net::ERR_IO_PENDING
)
214 void PnaclTranslationCacheEntry::CloseEntry(int rv
) {
217 LOG(ERROR
) << "Failed to close entry: " << net::ErrorToString(rv
);
220 BrowserThread::PostTask(
221 BrowserThread::IO
, FROM_HERE
, base::Bind(&CloseDiskCacheEntry
, entry_
));
225 void PnaclTranslationCacheEntry::Finish(int rv
) {
228 if (!read_callback_
.is_null()) {
229 BrowserThread::PostTask(BrowserThread::IO
,
231 base::Bind(read_callback_
, rv
, io_buf_
));
234 if (!write_callback_
.is_null()) {
235 BrowserThread::PostTask(
236 BrowserThread::IO
, FROM_HERE
, base::Bind(write_callback_
, rv
));
239 cache_
->OpComplete(this);
242 void PnaclTranslationCacheEntry::DispatchNext(int rv
) {
243 DCHECK(thread_checker_
.CalledOnValidThread());
250 LOG(ERROR
) << "DispatchNext called uninitialized";
255 step_
= TRANSFER_ENTRY
;
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
);
262 WriteEntry(0, io_buf_
->size());
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
);
270 // Just a cache miss, not necessarily an error.
274 step_
= CREATE_ENTRY
;
282 step_
= TRANSFER_ENTRY
;
283 WriteEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining());
285 LOG(ERROR
) << "Failed to Create Entry: " << net::ErrorToString(rv
);
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
);
300 io_buf_
->DidConsume(rv
);
301 if (io_buf_
->BytesRemaining() > 0) {
303 ? ReadEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining())
304 : WriteEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining());
308 // rv == 0 or we fell through (i.e. we have transferred all the bytes)
310 DCHECK(io_buf_
->BytesConsumed() == io_buf_
->size());
312 io_buf_
->SetOffset(0);
317 step_
= UNINITIALIZED
;
322 //////////////////////////////////////////////////////////////////////
323 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry
* entry
) {
324 open_entries_
.erase(entry
);
327 //////////////////////////////////////////////////////////////////////
328 // Construction and cache backend initialization
329 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
331 PnaclTranslationCache::~PnaclTranslationCache() {}
333 int PnaclTranslationCache::Init(net::CacheType cache_type
,
334 const base::FilePath
& cache_dir
,
336 const CompletionCallback
& callback
) {
337 int rv
= disk_cache::CreateCacheBackend(
339 net::CACHE_BACKEND_DEFAULT
,
342 true /* force_initialize */,
343 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE
).get(),
344 NULL
, /* dummy net log */
346 base::Bind(&PnaclTranslationCache::OnCreateBackendComplete
, AsWeakPtr()));
347 if (rv
== net::ERR_IO_PENDING
) {
348 init_callback_
= callback
;
353 void PnaclTranslationCache::OnCreateBackendComplete(int rv
) {
355 LOG(ERROR
) << "Backend init failed:" << net::ErrorToString(rv
);
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
));
364 //////////////////////////////////////////////////////////////////////
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
;
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
;
384 int PnaclTranslationCache::InitOnDisk(const base::FilePath
& cache_directory
,
385 const CompletionCallback
& callback
) {
387 return Init(net::PNACL_CACHE
, cache_directory
, 0 /* auto size */, callback
);
390 int PnaclTranslationCache::InitInMemory(const CompletionCallback
& callback
) {
392 return Init(net::MEMORY_CACHE
, base::FilePath(), kMaxMemCacheSize
, callback
);
395 int PnaclTranslationCache::Size() {
398 return disk_cache_
->GetEntryCount();
401 // Beware that any changes to this function or to PnaclCacheInfo will
402 // effectively invalidate existing translation cache entries.
405 std::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
) +
412 (info
.use_subzero
? "subzero;" : ";") + "URL:";
413 // Filter the username, password, and ref components from the URL
414 GURL::Replacements replacements
;
415 replacements
.ClearUsername();
416 replacements
.ClearPassword();
417 replacements
.ClearRef();
418 GURL
key_url(info
.pexe_url
.ReplaceComponents(replacements
));
419 retval
+= key_url
.spec() + ";";
420 // You would think that there is already code to format base::Time values
421 // somewhere, but I haven't found it yet. In any case, doing it ourselves
422 // here means we can keep the format stable.
423 base::Time::Exploded exploded
;
424 info
.last_modified
.UTCExplode(&exploded
);
425 if (info
.last_modified
.is_null() || !exploded
.HasValidValues()) {
426 memset(&exploded
, 0, sizeof(exploded
));
428 retval
+= "modified:" + IntToString(exploded
.year
) + ":" +
429 IntToString(exploded
.month
) + ":" +
430 IntToString(exploded
.day_of_month
) + ":" +
431 IntToString(exploded
.hour
) + ":" + IntToString(exploded
.minute
) +
432 ":" + IntToString(exploded
.second
) + ":" +
433 IntToString(exploded
.millisecond
) + ":UTC;";
434 retval
+= "etag:" + info
.etag
+ ";";
435 retval
+= "sandbox:" + info
.sandbox_isa
+ ";";
436 retval
+= "extra_flags:" + info
.extra_flags
+ ";";
440 int PnaclTranslationCache::DoomEntriesBetween(
443 const CompletionCallback
& callback
) {
444 return disk_cache_
->DoomEntriesBetween(initial
, end
, callback
);