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