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_host.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/debug/leak_annotations.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/logging.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/numerics/safe_math.h"
18 #include "components/nacl/browser/nacl_browser.h"
19 #include "components/nacl/browser/pnacl_translation_cache.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/io_buffer.h"
23 #include "net/base/net_errors.h"
24
25 using content::BrowserThread;
26
27 namespace {
28
29 static const base::FilePath::CharType kTranslationCacheDirectoryName[] =
30 FILE_PATH_LITERAL("PnaclTranslationCache");
31 // Delay to wait for initialization of the cache backend
32 static const int kTranslationCacheInitializationDelayMs = 20;
33
CloseBaseFile(base::File file)34 void CloseBaseFile(base::File file) {
35 base::ThreadPool::PostTask(FROM_HERE,
36 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
37 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
38 base::DoNothingWithBoundArgs(std::move(file)));
39 }
40
41 } // namespace
42
43 namespace pnacl {
44
45 class FileProxy {
46 public:
47 FileProxy(std::unique_ptr<base::File> file, PnaclHost* host);
48 int Write(scoped_refptr<net::DrainableIOBuffer> buffer);
49 void WriteDone(const PnaclHost::TranslationID& id, int result);
50
51 private:
52 std::unique_ptr<base::File> file_;
53 raw_ptr<PnaclHost> host_;
54 };
55
FileProxy(std::unique_ptr<base::File> file,PnaclHost * host)56 FileProxy::FileProxy(std::unique_ptr<base::File> file, PnaclHost* host)
57 : file_(std::move(file)), host_(host) {}
58
Write(scoped_refptr<net::DrainableIOBuffer> buffer)59 int FileProxy::Write(scoped_refptr<net::DrainableIOBuffer> buffer) {
60 int rv = file_->Write(0, buffer->data(), buffer->size());
61 if (rv == -1)
62 PLOG(ERROR) << "FileProxy::Write error";
63 return rv;
64 }
65
WriteDone(const PnaclHost::TranslationID & id,int result)66 void FileProxy::WriteDone(const PnaclHost::TranslationID& id, int result) {
67 host_->OnBufferCopiedToTempFile(id, std::move(file_), result);
68 }
69
70 PnaclHost::PnaclHost() = default;
71
GetInstance()72 PnaclHost* PnaclHost::GetInstance() {
73 static PnaclHost* instance = nullptr;
74 if (!instance) {
75 instance = new PnaclHost;
76 ANNOTATE_LEAKING_OBJECT_PTR(instance);
77 }
78 return instance;
79 }
80
PendingTranslation()81 PnaclHost::PendingTranslation::PendingTranslation()
82 : process_handle(base::kNullProcessHandle),
83 nexe_fd(nullptr),
84 got_nexe_fd(false),
85 got_cache_reply(false),
86 got_cache_hit(false),
87 is_incognito(false),
88 callback(NexeFdCallback()),
89 cache_info(nacl::PnaclCacheInfo()) {}
90
91 PnaclHost::PendingTranslation::PendingTranslation(
92 const PendingTranslation& other) = default;
93
~PendingTranslation()94 PnaclHost::PendingTranslation::~PendingTranslation() {
95 if (nexe_fd)
96 delete nexe_fd;
97 }
98
TranslationMayBeCached(const PendingTranslationMap::iterator & entry)99 bool PnaclHost::TranslationMayBeCached(
100 const PendingTranslationMap::iterator& entry) {
101 return !entry->second.is_incognito &&
102 !entry->second.cache_info.has_no_store_header;
103 }
104
105 /////////////////////////////////////// Initialization
106
GetCachePath()107 static base::FilePath GetCachePath() {
108 NaClBrowserDelegate* browser_delegate = nacl::NaClBrowser::GetDelegate();
109 // Determine where the translation cache resides in the file system. It
110 // exists in Chrome's cache directory and is not tied to any specific
111 // profile. If we fail, return an empty path.
112 // Start by finding the user data directory.
113 base::FilePath user_data_dir;
114 if (!browser_delegate ||
115 !browser_delegate->GetUserDirectory(&user_data_dir)) {
116 return base::FilePath();
117 }
118 // The cache directory may or may not be the user data directory.
119 base::FilePath cache_file_path;
120 browser_delegate->GetCacheDirectory(&cache_file_path);
121
122 // Append the base file name to the cache directory.
123 return cache_file_path.Append(kTranslationCacheDirectoryName);
124 }
125
OnCacheInitialized(int net_error)126 void PnaclHost::OnCacheInitialized(int net_error) {
127 DCHECK(thread_checker_.CalledOnValidThread());
128 // If the cache was cleared before the load completed, ignore.
129 if (cache_state_ == CacheReady)
130 return;
131 if (net_error != net::OK) {
132 // This will cause the cache to attempt to re-init on the next call to
133 // GetNexeFd.
134 cache_state_ = CacheUninitialized;
135 } else {
136 cache_state_ = CacheReady;
137 }
138 }
139
Init()140 void PnaclHost::Init() {
141 // Extra check that we're on the real UI thread since this version of
142 // Init isn't used in unit tests.
143 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
144 DCHECK(thread_checker_.CalledOnValidThread());
145 base::FilePath cache_path(GetCachePath());
146 if (cache_path.empty() || cache_state_ != CacheUninitialized)
147 return;
148 disk_cache_ = std::make_unique<PnaclTranslationCache>();
149 cache_state_ = CacheInitializing;
150 int rv = disk_cache_->InitOnDisk(
151 cache_path,
152 base::BindOnce(&PnaclHost::OnCacheInitialized, base::Unretained(this)));
153 if (rv != net::ERR_IO_PENDING)
154 OnCacheInitialized(rv);
155 }
156
157 // Initialize for testing, optionally using the in-memory backend, and manually
158 // setting the temporary file directory instead of using the system directory,
159 // and re-initializing file task runner.
InitForTest(base::FilePath temp_dir,bool in_memory)160 void PnaclHost::InitForTest(base::FilePath temp_dir, bool in_memory) {
161 DCHECK(thread_checker_.CalledOnValidThread());
162 file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
163 {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
164 disk_cache_ = std::make_unique<PnaclTranslationCache>();
165 cache_state_ = CacheInitializing;
166 temp_dir_ = temp_dir;
167 int rv;
168 if (in_memory) {
169 rv = disk_cache_->InitInMemory(
170 base::BindOnce(&PnaclHost::OnCacheInitialized, base::Unretained(this)));
171 } else {
172 rv = disk_cache_->InitOnDisk(
173 temp_dir,
174 base::BindOnce(&PnaclHost::OnCacheInitialized, base::Unretained(this)));
175 }
176 if (rv != net::ERR_IO_PENDING)
177 OnCacheInitialized(rv);
178 }
179
180 ///////////////////////////////////////// Temp files
181
182 // Create a temporary file on |file_task_runner_|.
183 // static
DoCreateTemporaryFile(base::FilePath temp_dir,TempFileCallback cb)184 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
185 TempFileCallback cb) {
186 base::FilePath file_path;
187 base::File file;
188 bool rv = temp_dir.empty()
189 ? base::CreateTemporaryFile(&file_path)
190 : base::CreateTemporaryFileInDir(temp_dir, &file_path);
191 if (!rv) {
192 PLOG(ERROR) << "Temp file creation failed.";
193 } else {
194 uint32_t flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
195 base::File::FLAG_WRITE | base::File::FLAG_WIN_TEMPORARY |
196 base::File::FLAG_DELETE_ON_CLOSE;
197 // This temporary file is being passed to an untrusted process.
198 flags = base::File::AddFlagsForPassingToUntrustedProcess(flags);
199 file.Initialize(file_path, flags);
200
201 if (!file.IsValid())
202 PLOG(ERROR) << "Temp file open failed: " << file.error_details();
203 }
204 content::GetUIThreadTaskRunner({})->PostTask(
205 FROM_HERE, base::BindOnce(cb, std::move(file)));
206 }
207
CreateTemporaryFile(TempFileCallback cb)208 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
209 if (!file_task_runner_->PostTask(
210 FROM_HERE,
211 base::BindOnce(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
212 DCHECK(thread_checker_.CalledOnValidThread());
213 cb.Run(base::File());
214 }
215 }
216
217 ///////////////////////////////////////// GetNexeFd implementation
218 ////////////////////// Common steps
219
GetNexeFd(int render_process_id,int pp_instance,bool is_incognito,const nacl::PnaclCacheInfo & cache_info,const NexeFdCallback & cb)220 void PnaclHost::GetNexeFd(int render_process_id,
221 int pp_instance,
222 bool is_incognito,
223 const nacl::PnaclCacheInfo& cache_info,
224 const NexeFdCallback& cb) {
225 DCHECK(thread_checker_.CalledOnValidThread());
226 if (cache_state_ == CacheUninitialized) {
227 Init();
228 }
229 if (cache_state_ != CacheReady) {
230 // If the backend hasn't yet initialized, try the request again later.
231 content::GetUIThreadTaskRunner({})->PostDelayedTask(
232 FROM_HERE,
233 base::BindOnce(&PnaclHost::GetNexeFd, base::Unretained(this),
234 render_process_id, pp_instance, is_incognito, cache_info,
235 cb),
236 base::Milliseconds(kTranslationCacheInitializationDelayMs));
237 return;
238 }
239
240 TranslationID id(render_process_id, pp_instance);
241 auto entry = pending_translations_.find(id);
242 if (entry != pending_translations_.end()) {
243 // Existing translation must have been abandonded. Clean it up.
244 LOG(ERROR) << "GetNexeFd for already-pending translation";
245 pending_translations_.erase(entry);
246 }
247
248 std::string cache_key(disk_cache_->GetKey(cache_info));
249 if (cache_key.empty()) {
250 LOG(ERROR) << "GetNexeFd: Invalid cache info";
251 cb.Run(base::File(), false);
252 return;
253 }
254
255 PendingTranslation pt;
256 pt.callback = cb;
257 pt.cache_info = cache_info;
258 pt.cache_key = cache_key;
259 pt.is_incognito = is_incognito;
260 pending_translations_[id] = pt;
261 SendCacheQueryAndTempFileRequest(cache_key, id);
262 }
263
264 // Dispatch the cache read request and the temp file creation request
265 // simultaneously; currently we need a temp file regardless of whether the
266 // request hits.
SendCacheQueryAndTempFileRequest(const std::string & cache_key,const TranslationID & id)267 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
268 const TranslationID& id) {
269 DCHECK(thread_checker_.CalledOnValidThread());
270 pending_backend_operations_++;
271 disk_cache_->GetNexe(cache_key, base::BindOnce(&PnaclHost::OnCacheQueryReturn,
272 base::Unretained(this), id));
273
274 CreateTemporaryFile(base::BindRepeating(&PnaclHost::OnTempFileReturn,
275 base::Unretained(this), id));
276 }
277
278 // Callback from the translation cache query. |id| is bound from
279 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
280 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
281 // by PnaclTranslationCache and now belongs to PnaclHost.
282 // (Bound callbacks must re-lookup the TranslationID because the translation
283 // could be cancelled before they get called).
OnCacheQueryReturn(const TranslationID & id,int net_error,scoped_refptr<net::DrainableIOBuffer> buffer)284 void PnaclHost::OnCacheQueryReturn(
285 const TranslationID& id,
286 int net_error,
287 scoped_refptr<net::DrainableIOBuffer> buffer) {
288 DCHECK(thread_checker_.CalledOnValidThread());
289 pending_backend_operations_--;
290 auto entry(pending_translations_.find(id));
291 if (entry == pending_translations_.end()) {
292 LOG(ERROR) << "OnCacheQueryReturn: id not found";
293 DeInitIfSafe();
294 return;
295 }
296 PendingTranslation* pt = &entry->second;
297 pt->got_cache_reply = true;
298 pt->got_cache_hit = (net_error == net::OK);
299 if (pt->got_cache_hit)
300 pt->nexe_read_buffer = buffer;
301 CheckCacheQueryReady(entry);
302 }
303
304 // Callback from temp file creation. |id| is bound from
305 // SendCacheQueryAndTempFileRequest, and |file| is the created file.
306 // If there was an error, file is invalid.
307 // (Bound callbacks must re-lookup the TranslationID because the translation
308 // could be cancelled before they get called).
OnTempFileReturn(const TranslationID & id,base::File file)309 void PnaclHost::OnTempFileReturn(const TranslationID& id,
310 base::File file) {
311 DCHECK(thread_checker_.CalledOnValidThread());
312 auto entry(pending_translations_.find(id));
313 if (entry == pending_translations_.end()) {
314 // The renderer may have signaled an error or closed while the temp
315 // file was being created.
316 LOG(ERROR) << "OnTempFileReturn: id not found";
317 CloseBaseFile(std::move(file));
318 return;
319 }
320 if (!file.IsValid()) {
321 // This translation will fail, but we need to retry any translation
322 // waiting for its result.
323 LOG(ERROR) << "OnTempFileReturn: temp file creation failed";
324 std::string key(entry->second.cache_key);
325 entry->second.callback.Run(base::File(), false);
326 bool may_be_cached = TranslationMayBeCached(entry);
327 pending_translations_.erase(entry);
328 // No translations will be waiting for entries that will not be stored.
329 if (may_be_cached)
330 RequeryMatchingTranslations(key);
331 return;
332 }
333 PendingTranslation* pt = &entry->second;
334 pt->got_nexe_fd = true;
335 pt->nexe_fd = new base::File(std::move(file));
336 CheckCacheQueryReady(entry);
337 }
338
339 // Check whether both the cache query and the temp file have returned, and check
340 // whether we actually got a hit or not.
CheckCacheQueryReady(const PendingTranslationMap::iterator & entry)341 void PnaclHost::CheckCacheQueryReady(
342 const PendingTranslationMap::iterator& entry) {
343 DCHECK(thread_checker_.CalledOnValidThread());
344 PendingTranslation* pt = &entry->second;
345 if (!(pt->got_cache_reply && pt->got_nexe_fd))
346 return;
347 if (!pt->got_cache_hit) {
348 // Check if there is already a pending translation for this file. If there
349 // is, we will wait for it to come back, to avoid redundant translations.
350 for (auto it = pending_translations_.begin();
351 it != pending_translations_.end(); ++it) {
352 // Another translation matches if it's a request for the same file,
353 if (it->second.cache_key == entry->second.cache_key &&
354 // and it's not this translation,
355 it->first != entry->first &&
356 // and it can be stored in the cache,
357 TranslationMayBeCached(it) &&
358 // and it's already gotten past this check and returned the miss.
359 it->second.got_cache_reply &&
360 it->second.got_nexe_fd) {
361 return;
362 }
363 }
364 ReturnMiss(entry);
365 return;
366 }
367
368 std::unique_ptr<base::File> file(pt->nexe_fd);
369 pt->nexe_fd = nullptr;
370 pt->got_nexe_fd = false;
371 FileProxy* proxy(new FileProxy(std::move(file), this));
372
373 base::ThreadPool::PostTaskAndReplyWithResult(
374 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
375 base::BindOnce(&FileProxy::Write, base::Unretained(proxy),
376 pt->nexe_read_buffer),
377 base::BindOnce(&FileProxy::WriteDone, base::Owned(proxy), entry->first));
378 }
379
380 //////////////////// GetNexeFd miss path
381 // Return the temp fd to the renderer, reporting a miss.
ReturnMiss(const PendingTranslationMap::iterator & entry)382 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
383 // Return the fd
384 PendingTranslation* pt = &entry->second;
385 NexeFdCallback cb(pt->callback);
386 cb.Run(*pt->nexe_fd, false);
387 if (!pt->nexe_fd->IsValid()) {
388 // Bad FD is unrecoverable, so clear out the entry.
389 pending_translations_.erase(entry);
390 }
391 }
392
393 // On error, just return a null refptr.
394 // static
CopyFileToBuffer(std::unique_ptr<base::File> file)395 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
396 std::unique_ptr<base::File> file) {
397 scoped_refptr<net::DrainableIOBuffer> buffer;
398
399 // TODO(eroman): Maximum size should be changed to size_t once that is
400 // what IOBuffer requires. crbug.com/488553. Also I don't think the
401 // max size should be inclusive here...
402 int64_t file_size = file->GetLength();
403 if (file_size < 0 || file_size > std::numeric_limits<int>::max()) {
404 PLOG(ERROR) << "Get file length failed " << file_size;
405 return buffer;
406 }
407
408 buffer = base::MakeRefCounted<net::DrainableIOBuffer>(
409 base::MakeRefCounted<net::IOBuffer>(
410 base::checked_cast<size_t>(file_size)),
411 base::checked_cast<size_t>(file_size));
412 if (file->Read(0, buffer->data(), buffer->size()) != file_size) {
413 PLOG(ERROR) << "CopyFileToBuffer file read failed";
414 buffer = nullptr;
415 }
416 return buffer;
417 }
418
419 // Called by the renderer in the miss path to report a finished translation
TranslationFinished(int render_process_id,int pp_instance,bool success)420 void PnaclHost::TranslationFinished(int render_process_id,
421 int pp_instance,
422 bool success) {
423 DCHECK(thread_checker_.CalledOnValidThread());
424 if (cache_state_ != CacheReady)
425 return;
426 TranslationID id(render_process_id, pp_instance);
427 auto entry(pending_translations_.find(id));
428 if (entry == pending_translations_.end()) {
429 LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id
430 << "," << pp_instance << " not found.";
431 return;
432 }
433 bool store_nexe = true;
434 // If this is a premature response (i.e. we haven't returned a temp file
435 // yet) or if it's an unsuccessful translation, or if we are incognito,
436 // don't store in the cache.
437 // TODO(dschuff): use a separate in-memory cache for incognito
438 // translations.
439 if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
440 !success || !TranslationMayBeCached(entry)) {
441 store_nexe = false;
442 } else {
443 std::unique_ptr<base::File> file(entry->second.nexe_fd);
444 entry->second.nexe_fd = nullptr;
445 entry->second.got_nexe_fd = false;
446
447 base::ThreadPool::PostTaskAndReplyWithResult(
448 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
449 base::BindOnce(&PnaclHost::CopyFileToBuffer, std::move(file)),
450 base::BindOnce(&PnaclHost::StoreTranslatedNexe, base::Unretained(this),
451 id));
452 }
453
454 if (!store_nexe) {
455 // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
456 if (entry->second.got_nexe_fd) {
457 std::unique_ptr<base::File> file(entry->second.nexe_fd);
458 entry->second.nexe_fd = nullptr;
459 CloseBaseFile(std::move(*file.get()));
460 }
461 pending_translations_.erase(entry);
462 }
463 }
464
465 // Store the translated nexe in the translation cache. Called back with the
466 // TranslationID from the host and the result of CopyFileToBuffer.
467 // (Bound callbacks must re-lookup the TranslationID because the translation
468 // could be cancelled before they get called).
StoreTranslatedNexe(TranslationID id,scoped_refptr<net::DrainableIOBuffer> buffer)469 void PnaclHost::StoreTranslatedNexe(
470 TranslationID id,
471 scoped_refptr<net::DrainableIOBuffer> buffer) {
472 DCHECK(thread_checker_.CalledOnValidThread());
473 if (cache_state_ != CacheReady)
474 return;
475 auto it(pending_translations_.find(id));
476 if (it == pending_translations_.end()) {
477 LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << ","
478 << id.second << " not found.";
479 return;
480 }
481
482 if (!buffer.get()) {
483 LOG(ERROR) << "Error reading translated nexe";
484 return;
485 }
486 pending_backend_operations_++;
487 disk_cache_->StoreNexe(it->second.cache_key, buffer.get(),
488 base::BindOnce(&PnaclHost::OnTranslatedNexeStored,
489 base::Unretained(this), it->first));
490 }
491
492 // After we know the nexe has been stored, we can clean up, and unblock any
493 // outstanding requests for the same file.
494 // (Bound callbacks must re-lookup the TranslationID because the translation
495 // could be cancelled before they get called).
OnTranslatedNexeStored(const TranslationID & id,int net_error)496 void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) {
497 auto entry(pending_translations_.find(id));
498 pending_backend_operations_--;
499 if (entry == pending_translations_.end()) {
500 // If the renderer closed while we were storing the nexe, we land here.
501 // Make sure we try to de-init.
502 DeInitIfSafe();
503 return;
504 }
505 std::string key(entry->second.cache_key);
506 pending_translations_.erase(entry);
507 RequeryMatchingTranslations(key);
508 }
509
510 // Check if any pending translations match |key|. If so, re-issue the cache
511 // query. In the overlapped miss case, we expect a hit this time, but a miss
512 // is also possible in case of an error.
RequeryMatchingTranslations(const std::string & key)513 void PnaclHost::RequeryMatchingTranslations(const std::string& key) {
514 DCHECK(thread_checker_.CalledOnValidThread());
515 // Check for outstanding misses to this same file
516 for (auto it = pending_translations_.begin();
517 it != pending_translations_.end(); ++it) {
518 if (it->second.cache_key == key) {
519 // Re-send the cache read request. This time we expect a hit, but if
520 // something goes wrong, it will just handle it like a miss.
521 it->second.got_cache_reply = false;
522 pending_backend_operations_++;
523 disk_cache_->GetNexe(key,
524 base::BindOnce(&PnaclHost::OnCacheQueryReturn,
525 base::Unretained(this), it->first));
526 }
527 }
528 }
529
530 //////////////////// GetNexeFd hit path
531
OnBufferCopiedToTempFile(const TranslationID & id,std::unique_ptr<base::File> file,int file_error)532 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
533 std::unique_ptr<base::File> file,
534 int file_error) {
535 DCHECK(thread_checker_.CalledOnValidThread());
536 auto entry(pending_translations_.find(id));
537 if (entry == pending_translations_.end()) {
538 CloseBaseFile(std::move(*file.get()));
539 return;
540 }
541 if (file_error == -1) {
542 // Write error on the temp file. Request a new file and start over.
543 CloseBaseFile(std::move(*file.get()));
544 CreateTemporaryFile(base::BindRepeating(
545 &PnaclHost::OnTempFileReturn, base::Unretained(this), entry->first));
546 return;
547 }
548 entry->second.callback.Run(*file.get(), true);
549 CloseBaseFile(std::move(*file.get()));
550 pending_translations_.erase(entry);
551 }
552
553 ///////////////////
554
RendererClosing(int render_process_id)555 void PnaclHost::RendererClosing(int render_process_id) {
556 DCHECK(thread_checker_.CalledOnValidThread());
557 if (cache_state_ != CacheReady)
558 return;
559 for (auto it = pending_translations_.begin();
560 it != pending_translations_.end();) {
561 auto to_erase(it++);
562 if (to_erase->first.first == render_process_id) {
563 // Clean up the open files.
564 if (to_erase->second.nexe_fd) {
565 std::unique_ptr<base::File> file(to_erase->second.nexe_fd);
566 to_erase->second.nexe_fd = nullptr;
567 CloseBaseFile(std::move(*file.get()));
568 }
569 std::string key(to_erase->second.cache_key);
570 bool may_be_cached = TranslationMayBeCached(to_erase);
571 pending_translations_.erase(to_erase);
572 // No translations will be waiting for entries that will not be stored.
573 if (may_be_cached)
574 RequeryMatchingTranslations(key);
575 }
576 }
577 DeInitIfSafe();
578 }
579
580 ////////////////// Cache data removal
ClearTranslationCacheEntriesBetween(base::Time initial_time,base::Time end_time,base::OnceClosure callback)581 void PnaclHost::ClearTranslationCacheEntriesBetween(
582 base::Time initial_time,
583 base::Time end_time,
584 base::OnceClosure callback) {
585 DCHECK(thread_checker_.CalledOnValidThread());
586 if (cache_state_ == CacheUninitialized) {
587 Init();
588 }
589 if (cache_state_ == CacheInitializing) {
590 // If the backend hasn't yet initialized, try the request again later.
591 content::GetUIThreadTaskRunner({})->PostDelayedTask(
592 FROM_HERE,
593 base::BindOnce(&PnaclHost::ClearTranslationCacheEntriesBetween,
594 base::Unretained(this), initial_time, end_time,
595 std::move(callback)),
596 base::Milliseconds(kTranslationCacheInitializationDelayMs));
597 return;
598 }
599 pending_backend_operations_++;
600
601 auto split_callback = base::SplitOnceCallback(std::move(callback));
602 int rv = disk_cache_->DoomEntriesBetween(
603 initial_time, end_time,
604 base::BindOnce(&PnaclHost::OnEntriesDoomed, base::Unretained(this),
605 std::move(split_callback.first)));
606 if (rv != net::ERR_IO_PENDING)
607 OnEntriesDoomed(std::move(split_callback.second), rv);
608 }
609
OnEntriesDoomed(base::OnceClosure callback,int net_error)610 void PnaclHost::OnEntriesDoomed(base::OnceClosure callback, int net_error) {
611 DCHECK(thread_checker_.CalledOnValidThread());
612 content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
613 pending_backend_operations_--;
614 // When clearing the cache, the UI is blocked on all the cache-clearing
615 // operations, and freeing the backend actually blocks the IO thread. So
616 // instead of calling DeInitIfSafe directly, post it for later.
617 content::GetUIThreadTaskRunner({})->PostTask(
618 FROM_HERE,
619 base::BindOnce(&PnaclHost::DeInitIfSafe, base::Unretained(this)));
620 }
621
622 // Destroying the cache backend causes it to post tasks to the cache thread to
623 // flush to disk. PnaclHost is leaked on shutdown because registering it as a
624 // Singleton with AtExitManager would result in it not being destroyed until all
625 // the browser threads have gone away and it's too late to post anything
626 // (attempting to do so hangs shutdown) at that point anyways. So we make sure
627 // to destroy it when we no longer have any outstanding operations that need it.
628 // These include pending translations, cache clear requests, and requests to
629 // read or write translated nexes. We check when renderers close, when cache
630 // clear requests finish, and when backend operations complete.
631
632 // It is not safe to delete the backend while it is initializing, nor if it has
633 // outstanding entry open requests; it is in theory safe to delete it with
634 // outstanding read/write requests, but because that distinction is hidden
635 // inside PnaclTranslationCache, we do not delete the backend if there are any
636 // backend requests in flight. As a last resort in the destructor, we just leak
637 // the backend to avoid hanging shutdown.
DeInitIfSafe()638 void PnaclHost::DeInitIfSafe() {
639 DCHECK(pending_backend_operations_ >= 0);
640 if (pending_translations_.empty() &&
641 pending_backend_operations_ <= 0 &&
642 cache_state_ == CacheReady) {
643 cache_state_ = CacheUninitialized;
644 disk_cache_.reset();
645 }
646 }
647
648 } // namespace pnacl
649