1 // Copyright 2013 The Chromium Authors
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.h>
8
9 #include <string>
10 #include <utility>
11
12 #include "base/files/file_path.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback.h"
15 #include "base/i18n/time_formatting.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/thread_checker.h"
20 #include "components/nacl/common/pnacl_types.h"
21 #include "content/public/browser/browser_task_traits.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "net/base/io_buffer.h"
24 #include "net/base/net_errors.h"
25 #include "net/disk_cache/disk_cache.h"
26 #include "third_party/icu/source/i18n/unicode/timezone.h"
27
28 namespace pnacl {
29
30 // This is in pnacl namespace instead of static so it can be used by the unit
31 // test.
32 constexpr 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::RefCountedThreadSafe<PnaclTranslationCacheEntry> {
43 public:
44 static PnaclTranslationCacheEntry* GetReadEntry(
45 base::WeakPtr<PnaclTranslationCache> cache,
46 const std::string& key,
47 GetNexeCallback callback);
48 static PnaclTranslationCacheEntry* GetWriteEntry(
49 base::WeakPtr<PnaclTranslationCache> cache,
50 const std::string& key,
51 net::DrainableIOBuffer* write_nexe,
52 CompletionOnceCallback callback);
53
54 PnaclTranslationCacheEntry(const PnaclTranslationCacheEntry&) = delete;
55 PnaclTranslationCacheEntry& operator=(const PnaclTranslationCacheEntry&) =
56 delete;
57
58 void Start();
59
60 // Writes: ---
61 // v |
62 // Start -> Open Existing --------------> Write ---> Close
63 // \ ^
64 // \ /
65 // --> Create --
66 // Reads:
67 // Start -> Open --------Read ----> Close
68 // | ^
69 // |__|
70 enum CacheStep {
71 UNINITIALIZED,
72 OPEN_ENTRY,
73 CREATE_ENTRY,
74 TRANSFER_ENTRY,
75 CLOSE_ENTRY,
76 FINISHED
77 };
78
79 private:
80 friend class base::RefCountedThreadSafe<PnaclTranslationCacheEntry>;
81 PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,
82 const std::string& key,
83 bool is_read);
84 ~PnaclTranslationCacheEntry();
85
86 // Try to open an existing entry in the backend
87 void OpenEntry();
88 // Create a new entry in the backend (for writes)
89 void CreateEntry();
90 // Write |len| bytes to the backend, starting at |offset|
91 void WriteEntry(int offset, int len);
92 // Read |len| bytes from the backend, starting at |offset|
93 void ReadEntry(int offset, int len);
94 // If there was an error, doom the entry. Then post a task to the IO
95 // thread to close (and delete) it.
96 void CloseEntry(int rv);
97 // Call the user callback, and signal to the cache to delete this.
98 void Finish(int rv);
99 // Used as the callback for all operations to the backend except those that
100 // first open/create entries. Handle state transitions, track bytes
101 // transferred, and call the other helper methods.
102 void DispatchNext(int rv);
103 // Like above but for first opening or creating of |entry_|.
104 void SaveEntryAndDispatchNext(disk_cache::EntryResult result);
105
106 base::WeakPtr<PnaclTranslationCache> cache_;
107 std::string key_;
108 raw_ptr<disk_cache::Entry> entry_;
109 CacheStep step_;
110 bool is_read_;
111 GetNexeCallback read_callback_;
112 CompletionOnceCallback write_callback_;
113 scoped_refptr<net::DrainableIOBuffer> io_buf_;
114 base::ThreadChecker thread_checker_;
115 };
116
117 // static
GetReadEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,GetNexeCallback callback)118 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry(
119 base::WeakPtr<PnaclTranslationCache> cache,
120 const std::string& key,
121 GetNexeCallback callback) {
122 PnaclTranslationCacheEntry* entry(
123 new PnaclTranslationCacheEntry(cache, key, true));
124 entry->read_callback_ = std::move(callback);
125 return entry;
126 }
127
128 // static
GetWriteEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,net::DrainableIOBuffer * write_nexe,CompletionOnceCallback callback)129 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry(
130 base::WeakPtr<PnaclTranslationCache> cache,
131 const std::string& key,
132 net::DrainableIOBuffer* write_nexe,
133 CompletionOnceCallback callback) {
134 PnaclTranslationCacheEntry* entry(
135 new PnaclTranslationCacheEntry(cache, key, false));
136 entry->io_buf_ = write_nexe;
137 entry->write_callback_ = std::move(callback);
138 return entry;
139 }
140
PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,bool is_read)141 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
142 base::WeakPtr<PnaclTranslationCache> cache,
143 const std::string& key,
144 bool is_read)
145 : cache_(cache),
146 key_(key),
147 entry_(nullptr),
148 step_(UNINITIALIZED),
149 is_read_(is_read) {}
150
~PnaclTranslationCacheEntry()151 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
152 // Ensure we have called the user's callback
153 if (step_ != FINISHED) {
154 if (!read_callback_.is_null()) {
155 content::GetUIThreadTaskRunner({})->PostTask(
156 FROM_HERE, base::BindOnce(std::move(read_callback_), net::ERR_ABORTED,
157 scoped_refptr<net::DrainableIOBuffer>()));
158 }
159 if (!write_callback_.is_null()) {
160 content::GetUIThreadTaskRunner({})->PostTask(
161 FROM_HERE,
162 base::BindOnce(std::move(write_callback_), net::ERR_ABORTED));
163 }
164 }
165 }
166
Start()167 void PnaclTranslationCacheEntry::Start() {
168 DCHECK(thread_checker_.CalledOnValidThread());
169 step_ = OPEN_ENTRY;
170 OpenEntry();
171 }
172
173 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called
174 // from DispatchNext, so they know that cache_ is still valid.
OpenEntry()175 void PnaclTranslationCacheEntry::OpenEntry() {
176 disk_cache::EntryResult result = cache_->backend()->OpenEntry(
177 key_, net::HIGHEST,
178 base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext,
179 this));
180 if (result.net_error() != net::ERR_IO_PENDING)
181 SaveEntryAndDispatchNext(std::move(result));
182 }
183
CreateEntry()184 void PnaclTranslationCacheEntry::CreateEntry() {
185 disk_cache::EntryResult result = cache_->backend()->CreateEntry(
186 key_, net::HIGHEST,
187 base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext,
188 this));
189 if (result.net_error() != net::ERR_IO_PENDING)
190 SaveEntryAndDispatchNext(std::move(result));
191 }
192
WriteEntry(int offset,int len)193 void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) {
194 DCHECK(io_buf_->BytesRemaining() == len);
195 int rv = entry_->WriteData(
196 1, offset, io_buf_.get(), len,
197 base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this), false);
198 if (rv != net::ERR_IO_PENDING)
199 DispatchNext(rv);
200 }
201
ReadEntry(int offset,int len)202 void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) {
203 int rv = entry_->ReadData(
204 1, offset, io_buf_.get(), len,
205 base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this));
206 if (rv != net::ERR_IO_PENDING)
207 DispatchNext(rv);
208 }
209
CloseEntry(int rv)210 void PnaclTranslationCacheEntry::CloseEntry(int rv) {
211 DCHECK(entry_);
212 if (rv < 0) {
213 LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv);
214 entry_->Doom();
215 }
216 content::GetUIThreadTaskRunner({})->PostTask(
217 FROM_HERE,
218 base::BindOnce(&disk_cache::Entry::Close, base::Unretained(entry_)));
219 Finish(rv);
220 }
221
Finish(int rv)222 void PnaclTranslationCacheEntry::Finish(int rv) {
223 step_ = FINISHED;
224 if (is_read_) {
225 if (!read_callback_.is_null()) {
226 content::GetUIThreadTaskRunner({})->PostTask(
227 FROM_HERE, base::BindOnce(std::move(read_callback_), rv, io_buf_));
228 }
229 } else {
230 if (!write_callback_.is_null()) {
231 content::GetUIThreadTaskRunner({})->PostTask(
232 FROM_HERE, base::BindOnce(std::move(write_callback_), rv));
233 }
234 }
235 cache_->OpComplete(this);
236 }
237
DispatchNext(int rv)238 void PnaclTranslationCacheEntry::DispatchNext(int rv) {
239 DCHECK(thread_checker_.CalledOnValidThread());
240 if (!cache_)
241 return;
242
243 switch (step_) {
244 case UNINITIALIZED:
245 case FINISHED:
246 LOG(ERROR) << "DispatchNext called uninitialized";
247 break;
248
249 case OPEN_ENTRY:
250 if (rv == net::OK) {
251 step_ = TRANSFER_ENTRY;
252 if (is_read_) {
253 int bytes_to_transfer = entry_->GetDataSize(1);
254 io_buf_ = base::MakeRefCounted<net::DrainableIOBuffer>(
255 base::MakeRefCounted<net::IOBufferWithSize>(bytes_to_transfer),
256 bytes_to_transfer);
257 ReadEntry(0, bytes_to_transfer);
258 } else {
259 WriteEntry(0, io_buf_->size());
260 }
261 } else {
262 if (rv != net::ERR_FAILED) {
263 // ERROR_FAILED is what we expect if the entry doesn't exist.
264 LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv);
265 }
266 if (is_read_) {
267 // Just a cache miss, not necessarily an error.
268 entry_ = nullptr;
269 Finish(rv);
270 } else {
271 step_ = CREATE_ENTRY;
272 CreateEntry();
273 }
274 }
275 break;
276
277 case CREATE_ENTRY:
278 if (rv == net::OK) {
279 step_ = TRANSFER_ENTRY;
280 WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
281 } else {
282 LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv);
283 Finish(rv);
284 }
285 break;
286
287 case TRANSFER_ENTRY:
288 if (rv < 0) {
289 // We do not call DispatchNext directly if WriteEntry/ReadEntry returns
290 // ERR_IO_PENDING, and the callback should not return that value either.
291 LOG(ERROR) << "Failed to complete write to entry: "
292 << net::ErrorToString(rv);
293 step_ = CLOSE_ENTRY;
294 CloseEntry(rv);
295 break;
296 } else if (rv > 0) {
297 io_buf_->DidConsume(rv);
298 if (io_buf_->BytesRemaining() > 0) {
299 if (is_read_) {
300 ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
301 } else {
302 WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
303 }
304 break;
305 }
306 }
307 // rv == 0 or we fell through (i.e. we have transferred all the bytes)
308 step_ = CLOSE_ENTRY;
309 DCHECK(io_buf_->BytesConsumed() == io_buf_->size());
310 if (is_read_)
311 io_buf_->SetOffset(0);
312 CloseEntry(0);
313 break;
314
315 case CLOSE_ENTRY:
316 step_ = UNINITIALIZED;
317 break;
318 }
319 }
320
SaveEntryAndDispatchNext(disk_cache::EntryResult result)321 void PnaclTranslationCacheEntry::SaveEntryAndDispatchNext(
322 disk_cache::EntryResult result) {
323 int rv = result.net_error();
324 entry_ = result.ReleaseEntry();
325 DispatchNext(rv);
326 }
327
328 //////////////////////////////////////////////////////////////////////
OpComplete(PnaclTranslationCacheEntry * entry)329 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
330 open_entries_.erase(entry);
331 }
332
333 //////////////////////////////////////////////////////////////////////
334 // Construction and cache backend initialization
PnaclTranslationCache()335 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
336
~PnaclTranslationCache()337 PnaclTranslationCache::~PnaclTranslationCache() {}
338
Init(net::CacheType cache_type,const base::FilePath & cache_dir,int cache_size,CompletionOnceCallback callback)339 int PnaclTranslationCache::Init(net::CacheType cache_type,
340 const base::FilePath& cache_dir,
341 int cache_size,
342 CompletionOnceCallback callback) {
343 disk_cache::BackendResult result = disk_cache::CreateCacheBackend(
344 cache_type, net::CACHE_BACKEND_DEFAULT, /*file_operations=*/nullptr,
345 cache_dir, cache_size, disk_cache::ResetHandling::kResetOnError,
346 nullptr, /* dummy net log */
347 base::BindOnce(&PnaclTranslationCache::OnCreateBackendComplete,
348 weak_ptr_factory_.GetWeakPtr()));
349 if (result.net_error == net::OK) {
350 disk_cache_ = std::move(result.backend);
351 } else if (result.net_error == net::ERR_IO_PENDING) {
352 init_callback_ = std::move(callback);
353 }
354 return result.net_error;
355 }
356
OnCreateBackendComplete(disk_cache::BackendResult result)357 void PnaclTranslationCache::OnCreateBackendComplete(
358 disk_cache::BackendResult result) {
359 if (result.net_error < 0) {
360 LOG(ERROR) << "Backend init failed:"
361 << net::ErrorToString(result.net_error);
362 }
363 disk_cache_ = std::move(result.backend);
364 // Invoke our client's callback function.
365 if (!init_callback_.is_null()) {
366 content::GetUIThreadTaskRunner({})->PostTask(
367 FROM_HERE, base::BindOnce(std::move(init_callback_), result.net_error));
368 }
369 }
370
371 //////////////////////////////////////////////////////////////////////
372 // High-level API
373
StoreNexe(const std::string & key,net::DrainableIOBuffer * nexe_data,CompletionOnceCallback callback)374 void PnaclTranslationCache::StoreNexe(const std::string& key,
375 net::DrainableIOBuffer* nexe_data,
376 CompletionOnceCallback callback) {
377 PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry(
378 weak_ptr_factory_.GetWeakPtr(), key, nexe_data, std::move(callback));
379 open_entries_[entry] = entry;
380 entry->Start();
381 }
382
GetNexe(const std::string & key,GetNexeCallback callback)383 void PnaclTranslationCache::GetNexe(const std::string& key,
384 GetNexeCallback callback) {
385 PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetReadEntry(
386 weak_ptr_factory_.GetWeakPtr(), key, std::move(callback));
387 open_entries_[entry] = entry;
388 entry->Start();
389 }
390
InitOnDisk(const base::FilePath & cache_directory,CompletionOnceCallback callback)391 int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory,
392 CompletionOnceCallback callback) {
393 in_memory_ = false;
394 return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */,
395 std::move(callback));
396 }
397
InitInMemory(CompletionOnceCallback callback)398 int PnaclTranslationCache::InitInMemory(CompletionOnceCallback callback) {
399 in_memory_ = true;
400 return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize,
401 std::move(callback));
402 }
403
Size()404 int PnaclTranslationCache::Size() {
405 return disk_cache_ ? disk_cache_->GetEntryCount() : -1;
406 }
407
408 // Beware that any changes to this function or to PnaclCacheInfo will
409 // effectively invalidate existing translation cache entries.
410
411 // static
GetKey(const nacl::PnaclCacheInfo & info)412 std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) {
413 if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0 ||
414 info.extra_flags.size() > 512) {
415 return std::string();
416 }
417
418 // Filter the username, password, and ref components from the URL.
419 GURL::Replacements replacements;
420 replacements.ClearUsername();
421 replacements.ClearPassword();
422 replacements.ClearRef();
423 const GURL key_url = info.pexe_url.ReplaceComponents(replacements);
424
425 const std::string timestamp = base::UnlocalizedTimeFormatWithPattern(
426 info.last_modified, "y:M:d:H:m:s:S:'UTC'", icu::TimeZone::getGMT());
427
428 return base::StringPrintf(
429 "ABI:%d;opt:%d%s;URL:%s;modified:%s;etag:%s;sandbox:%s;extra_flags:%s;",
430 info.abi_version, info.opt_level, info.use_subzero ? "subzero" : "",
431 key_url.spec().c_str(), timestamp.c_str(), info.etag.c_str(),
432 info.sandbox_isa.c_str(), info.extra_flags.c_str());
433 }
434
DoomEntriesBetween(base::Time initial,base::Time end,CompletionOnceCallback callback)435 int PnaclTranslationCache::DoomEntriesBetween(base::Time initial,
436 base::Time end,
437 CompletionOnceCallback callback) {
438 return disk_cache_->DoomEntriesBetween(initial, end, std::move(callback));
439 }
440
441 } // namespace pnacl
442