• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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