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/profiler/scoped_tracker.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/threading/thread_checker.h"
15 #include "components/nacl/common/pnacl_types.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "net/base/io_buffer.h"
18 #include "net/base/net_errors.h"
19 #include "net/disk_cache/disk_cache.h"
21 using base::IntToString
;
22 using content::BrowserThread
;
26 void CloseDiskCacheEntry(disk_cache::Entry
* entry
) { entry
->Close(); }
31 // This is in pnacl namespace instead of static so they can be used
33 const int kMaxMemCacheSize
= 100 * 1024 * 1024;
35 //////////////////////////////////////////////////////////////////////
36 // Handle Reading/Writing to Cache.
38 // PnaclTranslationCacheEntry is a shim that provides storage for the
39 // 'key' and 'data' strings as the disk_cache is performing various async
40 // operations. It also tracks the open disk_cache::Entry
41 // and ensures that the entry is closed.
42 class PnaclTranslationCacheEntry
43 : public base::RefCounted
<PnaclTranslationCacheEntry
> {
45 static PnaclTranslationCacheEntry
* GetReadEntry(
46 base::WeakPtr
<PnaclTranslationCache
> cache
,
47 const std::string
& key
,
48 const GetNexeCallback
& callback
);
49 static PnaclTranslationCacheEntry
* GetWriteEntry(
50 base::WeakPtr
<PnaclTranslationCache
> cache
,
51 const std::string
& key
,
52 net::DrainableIOBuffer
* write_nexe
,
53 const CompletionCallback
& callback
);
59 // Start -> Open Existing --------------> Write ---> Close
64 // Start -> Open --------Read ----> Close
77 friend class base::RefCounted
<PnaclTranslationCacheEntry
>;
78 PnaclTranslationCacheEntry(base::WeakPtr
<PnaclTranslationCache
> cache
,
79 const std::string
& key
,
81 ~PnaclTranslationCacheEntry();
83 // Try to open an existing entry in the backend
85 // Create a new entry in the backend (for writes)
87 // Write |len| bytes to the backend, starting at |offset|
88 void WriteEntry(int offset
, int len
);
89 // Read |len| bytes from the backend, starting at |offset|
90 void ReadEntry(int offset
, int len
);
91 // If there was an error, doom the entry. Then post a task to the IO
92 // thread to close (and delete) it.
93 void CloseEntry(int rv
);
94 // Call the user callback, and signal to the cache to delete this.
96 // Used as the callback for all operations to the backend. Handle state
97 // transitions, track bytes transferred, and call the other helper methods.
98 void DispatchNext(int rv
);
100 base::WeakPtr
<PnaclTranslationCache
> cache_
;
102 disk_cache::Entry
* entry_
;
105 GetNexeCallback read_callback_
;
106 CompletionCallback write_callback_
;
107 scoped_refptr
<net::DrainableIOBuffer
> io_buf_
;
108 base::ThreadChecker thread_checker_
;
109 DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry
);
113 PnaclTranslationCacheEntry
* PnaclTranslationCacheEntry::GetReadEntry(
114 base::WeakPtr
<PnaclTranslationCache
> cache
,
115 const std::string
& key
,
116 const GetNexeCallback
& callback
) {
117 PnaclTranslationCacheEntry
* entry(
118 new PnaclTranslationCacheEntry(cache
, key
, true));
119 entry
->read_callback_
= callback
;
124 PnaclTranslationCacheEntry
* PnaclTranslationCacheEntry::GetWriteEntry(
125 base::WeakPtr
<PnaclTranslationCache
> cache
,
126 const std::string
& key
,
127 net::DrainableIOBuffer
* write_nexe
,
128 const CompletionCallback
& callback
) {
129 PnaclTranslationCacheEntry
* entry(
130 new PnaclTranslationCacheEntry(cache
, key
, false));
131 entry
->io_buf_
= write_nexe
;
132 entry
->write_callback_
= callback
;
136 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
137 base::WeakPtr
<PnaclTranslationCache
> cache
,
138 const std::string
& key
,
143 step_(UNINITIALIZED
),
146 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
147 // Ensure we have called the user's callback
148 if (step_
!= FINISHED
) {
149 if (!read_callback_
.is_null()) {
150 BrowserThread::PostTask(
153 base::Bind(read_callback_
,
155 scoped_refptr
<net::DrainableIOBuffer
>()));
157 if (!write_callback_
.is_null()) {
158 BrowserThread::PostTask(BrowserThread::IO
,
160 base::Bind(write_callback_
, net::ERR_ABORTED
));
165 void PnaclTranslationCacheEntry::Start() {
166 DCHECK(thread_checker_
.CalledOnValidThread());
171 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called
172 // from DispatchNext, so they know that cache_ is still valid.
173 void PnaclTranslationCacheEntry::OpenEntry() {
174 int rv
= cache_
->backend()->OpenEntry(
177 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
178 if (rv
!= net::ERR_IO_PENDING
)
182 void PnaclTranslationCacheEntry::CreateEntry() {
183 int rv
= cache_
->backend()->CreateEntry(
186 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
187 if (rv
!= net::ERR_IO_PENDING
)
191 void PnaclTranslationCacheEntry::WriteEntry(int offset
, int len
) {
192 DCHECK(io_buf_
->BytesRemaining() == len
);
193 int rv
= entry_
->WriteData(
198 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this),
200 if (rv
!= net::ERR_IO_PENDING
)
204 void PnaclTranslationCacheEntry::ReadEntry(int offset
, int len
) {
205 int rv
= entry_
->ReadData(
210 base::Bind(&PnaclTranslationCacheEntry::DispatchNext
, this));
211 if (rv
!= net::ERR_IO_PENDING
)
215 void PnaclTranslationCacheEntry::CloseEntry(int rv
) {
218 LOG(ERROR
) << "Failed to close entry: " << net::ErrorToString(rv
);
221 BrowserThread::PostTask(
222 BrowserThread::IO
, FROM_HERE
, base::Bind(&CloseDiskCacheEntry
, entry_
));
226 void PnaclTranslationCacheEntry::Finish(int rv
) {
229 if (!read_callback_
.is_null()) {
230 BrowserThread::PostTask(BrowserThread::IO
,
232 base::Bind(read_callback_
, rv
, io_buf_
));
235 if (!write_callback_
.is_null()) {
236 BrowserThread::PostTask(
237 BrowserThread::IO
, FROM_HERE
, base::Bind(write_callback_
, rv
));
240 cache_
->OpComplete(this);
243 void PnaclTranslationCacheEntry::DispatchNext(int rv
) {
244 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed.
245 tracked_objects::ScopedTracker
tracking_profile(
246 FROM_HERE_WITH_EXPLICIT_FUNCTION(
247 "422516 PnaclTranslationCacheEntry::DispatchNext"));
249 DCHECK(thread_checker_
.CalledOnValidThread());
256 LOG(ERROR
) << "DispatchNext called uninitialized";
261 step_
= TRANSFER_ENTRY
;
263 int bytes_to_transfer
= entry_
->GetDataSize(1);
264 io_buf_
= new net::DrainableIOBuffer(
265 new net::IOBuffer(bytes_to_transfer
), bytes_to_transfer
);
266 ReadEntry(0, bytes_to_transfer
);
268 WriteEntry(0, io_buf_
->size());
271 if (rv
!= net::ERR_FAILED
) {
272 // ERROR_FAILED is what we expect if the entry doesn't exist.
273 LOG(ERROR
) << "OpenEntry failed: " << net::ErrorToString(rv
);
276 // Just a cache miss, not necessarily an error.
280 step_
= CREATE_ENTRY
;
288 step_
= TRANSFER_ENTRY
;
289 WriteEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining());
291 LOG(ERROR
) << "Failed to Create Entry: " << net::ErrorToString(rv
);
298 // We do not call DispatchNext directly if WriteEntry/ReadEntry returns
299 // ERR_IO_PENDING, and the callback should not return that value either.
300 LOG(ERROR
) << "Failed to complete write to entry: "
301 << net::ErrorToString(rv
);
306 io_buf_
->DidConsume(rv
);
307 if (io_buf_
->BytesRemaining() > 0) {
309 ? ReadEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining())
310 : WriteEntry(io_buf_
->BytesConsumed(), io_buf_
->BytesRemaining());
314 // rv == 0 or we fell through (i.e. we have transferred all the bytes)
316 DCHECK(io_buf_
->BytesConsumed() == io_buf_
->size());
318 io_buf_
->SetOffset(0);
323 step_
= UNINITIALIZED
;
328 //////////////////////////////////////////////////////////////////////
329 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry
* entry
) {
330 open_entries_
.erase(entry
);
333 //////////////////////////////////////////////////////////////////////
334 // Construction and cache backend initialization
335 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
337 PnaclTranslationCache::~PnaclTranslationCache() {}
339 int PnaclTranslationCache::Init(net::CacheType cache_type
,
340 const base::FilePath
& cache_dir
,
342 const CompletionCallback
& callback
) {
343 int rv
= disk_cache::CreateCacheBackend(
345 net::CACHE_BACKEND_DEFAULT
,
348 true /* force_initialize */,
349 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE
).get(),
350 NULL
, /* dummy net log */
352 base::Bind(&PnaclTranslationCache::OnCreateBackendComplete
, AsWeakPtr()));
353 if (rv
== net::ERR_IO_PENDING
) {
354 init_callback_
= callback
;
359 void PnaclTranslationCache::OnCreateBackendComplete(int rv
) {
361 LOG(ERROR
) << "Backend init failed:" << net::ErrorToString(rv
);
363 // Invoke our client's callback function.
364 if (!init_callback_
.is_null()) {
365 BrowserThread::PostTask(
366 BrowserThread::IO
, FROM_HERE
, base::Bind(init_callback_
, rv
));
370 //////////////////////////////////////////////////////////////////////
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
;
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
;
390 int PnaclTranslationCache::InitOnDisk(const base::FilePath
& cache_directory
,
391 const CompletionCallback
& callback
) {
393 return Init(net::PNACL_CACHE
, cache_directory
, 0 /* auto size */, callback
);
396 int PnaclTranslationCache::InitInMemory(const CompletionCallback
& callback
) {
398 return Init(net::MEMORY_CACHE
, base::FilePath(), kMaxMemCacheSize
, callback
);
401 int PnaclTranslationCache::Size() {
404 return disk_cache_
->GetEntryCount();
407 // Beware that any changes to this function or to PnaclCacheInfo will
408 // effectively invalidate existing translation cache entries.
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 info
.extra_flags
.size() > 512)
414 return std::string();
415 std::string
retval("ABI:");
416 retval
+= IntToString(info
.abi_version
) + ";" + "opt:" +
417 IntToString(info
.opt_level
) +
418 (info
.use_subzero
? "subzero;" : ";") + "URL:";
419 // Filter the username, password, and ref components from the URL
420 GURL::Replacements replacements
;
421 replacements
.ClearUsername();
422 replacements
.ClearPassword();
423 replacements
.ClearRef();
424 GURL
key_url(info
.pexe_url
.ReplaceComponents(replacements
));
425 retval
+= key_url
.spec() + ";";
426 // You would think that there is already code to format base::Time values
427 // somewhere, but I haven't found it yet. In any case, doing it ourselves
428 // here means we can keep the format stable.
429 base::Time::Exploded exploded
;
430 info
.last_modified
.UTCExplode(&exploded
);
431 if (info
.last_modified
.is_null() || !exploded
.HasValidValues()) {
432 memset(&exploded
, 0, sizeof(exploded
));
434 retval
+= "modified:" + IntToString(exploded
.year
) + ":" +
435 IntToString(exploded
.month
) + ":" +
436 IntToString(exploded
.day_of_month
) + ":" +
437 IntToString(exploded
.hour
) + ":" + IntToString(exploded
.minute
) +
438 ":" + IntToString(exploded
.second
) + ":" +
439 IntToString(exploded
.millisecond
) + ":UTC;";
440 retval
+= "etag:" + info
.etag
+ ";";
441 retval
+= "sandbox:" + info
.sandbox_isa
+ ";";
442 retval
+= "extra_flags:" + info
.extra_flags
+ ";";
446 int PnaclTranslationCache::DoomEntriesBetween(
449 const CompletionCallback
& callback
) {
450 return disk_cache_
->DoomEntriesBetween(initial
, end
, callback
);