1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/nacl/browser/pnacl_host.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/task_runner_util.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "components/nacl/browser/nacl_browser.h"
15 #include "components/nacl/browser/pnacl_translation_cache.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "net/base/io_buffer.h"
18 #include "net/base/net_errors.h"
20 using content::BrowserThread;
23 static const base::FilePath::CharType kTranslationCacheDirectoryName[] =
24 FILE_PATH_LITERAL("PnaclTranslationCache");
25 // Delay to wait for initialization of the cache backend
26 static const int kTranslationCacheInitializationDelayMs = 20;
31 PnaclHost::PnaclHost()
32 : pending_backend_operations_(0),
33 cache_state_(CacheUninitialized),
34 weak_factory_(this) {}
36 PnaclHost::~PnaclHost() {
37 // When PnaclHost is destroyed, it's too late to post anything to the cache
38 // thread (it will hang shutdown). So just leak the cache backend.
39 pnacl::PnaclTranslationCache* cache = disk_cache_.release();
43 PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); }
45 PnaclHost::PendingTranslation::PendingTranslation()
46 : process_handle(base::kNullProcessHandle),
48 nexe_fd(base::kInvalidPlatformFileValue),
50 got_cache_reply(false),
53 callback(NexeFdCallback()),
54 cache_info(nacl::PnaclCacheInfo()) {}
55 PnaclHost::PendingTranslation::~PendingTranslation() {}
57 bool PnaclHost::TranslationMayBeCached(
58 const PendingTranslationMap::iterator& entry) {
59 return !entry->second.is_incognito &&
60 !entry->second.cache_info.has_no_store_header;
63 /////////////////////////////////////// Initialization
65 static base::FilePath GetCachePath() {
66 NaClBrowserDelegate* browser_delegate = nacl::NaClBrowser::GetDelegate();
67 // Determine where the translation cache resides in the file system. It
68 // exists in Chrome's cache directory and is not tied to any specific
69 // profile. If we fail, return an empty path.
70 // Start by finding the user data directory.
71 base::FilePath user_data_dir;
72 if (!browser_delegate ||
73 !browser_delegate->GetUserDirectory(&user_data_dir)) {
74 return base::FilePath();
76 // The cache directory may or may not be the user data directory.
77 base::FilePath cache_file_path;
78 browser_delegate->GetCacheDirectory(&cache_file_path);
80 // Append the base file name to the cache directory.
81 return cache_file_path.Append(kTranslationCacheDirectoryName);
84 void PnaclHost::OnCacheInitialized(int net_error) {
85 DCHECK(thread_checker_.CalledOnValidThread());
86 // If the cache was cleared before the load completed, ignore.
87 if (cache_state_ == CacheReady)
89 if (net_error != net::OK) {
90 // This will cause the cache to attempt to re-init on the next call to
92 cache_state_ = CacheUninitialized;
94 cache_state_ = CacheReady;
98 void PnaclHost::Init() {
99 // Extra check that we're on the real IO thread since this version of
100 // Init isn't used in unit tests.
101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
102 DCHECK(thread_checker_.CalledOnValidThread());
103 base::FilePath cache_path(GetCachePath());
104 if (cache_path.empty() || cache_state_ != CacheUninitialized)
106 disk_cache_.reset(new pnacl::PnaclTranslationCache());
107 cache_state_ = CacheInitializing;
108 int rv = disk_cache_->InitOnDisk(
110 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
111 if (rv != net::ERR_IO_PENDING)
112 OnCacheInitialized(rv);
115 // Initialize for testing, optionally using the in-memory backend, and manually
116 // setting the temporary file directory instead of using the system directory.
117 void PnaclHost::InitForTest(base::FilePath temp_dir, bool in_memory) {
118 DCHECK(thread_checker_.CalledOnValidThread());
119 disk_cache_.reset(new pnacl::PnaclTranslationCache());
120 cache_state_ = CacheInitializing;
121 temp_dir_ = temp_dir;
124 rv = disk_cache_->InitInMemory(
125 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
127 rv = disk_cache_->InitOnDisk(
129 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
131 if (rv != net::ERR_IO_PENDING)
132 OnCacheInitialized(rv);
135 ///////////////////////////////////////// Temp files
137 // Create a temporary file on the blocking pool
139 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
140 TempFileCallback cb) {
141 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
143 base::FilePath file_path;
144 base::PlatformFile file_handle(base::kInvalidPlatformFileValue);
145 bool rv = temp_dir.empty()
146 ? base::CreateTemporaryFile(&file_path)
147 : base::CreateTemporaryFileInDir(temp_dir, &file_path);
149 PLOG(ERROR) << "Temp file creation failed.";
151 base::PlatformFileError error;
152 file_handle = base::CreatePlatformFile(
154 base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ |
155 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY |
156 base::PLATFORM_FILE_DELETE_ON_CLOSE,
160 if (error != base::PLATFORM_FILE_OK) {
161 PLOG(ERROR) << "Temp file open failed: " << error;
162 file_handle = base::kInvalidPlatformFileValue;
165 BrowserThread::PostTask(
166 BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle));
169 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
170 if (!BrowserThread::PostBlockingPoolSequencedTask(
171 "PnaclHostCreateTempFile",
173 base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
174 DCHECK(thread_checker_.CalledOnValidThread());
175 cb.Run(base::kInvalidPlatformFileValue);
179 ///////////////////////////////////////// GetNexeFd implementation
180 ////////////////////// Common steps
182 void PnaclHost::GetNexeFd(int render_process_id,
186 const nacl::PnaclCacheInfo& cache_info,
187 const NexeFdCallback& cb) {
188 DCHECK(thread_checker_.CalledOnValidThread());
189 if (cache_state_ == CacheUninitialized) {
192 if (cache_state_ != CacheReady) {
193 // If the backend hasn't yet initialized, try the request again later.
194 BrowserThread::PostDelayedTask(BrowserThread::IO,
196 base::Bind(&PnaclHost::GetNexeFd,
197 weak_factory_.GetWeakPtr(),
204 base::TimeDelta::FromMilliseconds(
205 kTranslationCacheInitializationDelayMs));
209 TranslationID id(render_process_id, pp_instance);
210 PendingTranslationMap::iterator entry = pending_translations_.find(id);
211 if (entry != pending_translations_.end()) {
212 // Existing translation must have been abandonded. Clean it up.
213 LOG(ERROR) << "GetNexeFd for already-pending translation";
214 pending_translations_.erase(entry);
217 std::string cache_key(disk_cache_->GetKey(cache_info));
218 if (cache_key.empty()) {
219 LOG(ERROR) << "GetNexeFd: Invalid cache info";
220 cb.Run(base::kInvalidPlatformFileValue, false);
224 PendingTranslation pt;
225 pt.render_view_id = render_view_id;
227 pt.cache_info = cache_info;
228 pt.cache_key = cache_key;
229 pt.is_incognito = is_incognito;
230 pending_translations_[id] = pt;
231 SendCacheQueryAndTempFileRequest(cache_key, id);
234 // Dispatch the cache read request and the temp file creation request
235 // simultaneously; currently we need a temp file regardless of whether the
237 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
238 const TranslationID& id) {
239 pending_backend_operations_++;
240 disk_cache_->GetNexe(
243 &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id));
246 base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id));
249 // Callback from the translation cache query. |id| is bound from
250 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
251 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
252 // by PnaclTranslationCache and now belongs to PnaclHost.
253 // (Bound callbacks must re-lookup the TranslationID because the translation
254 // could be cancelled before they get called).
255 void PnaclHost::OnCacheQueryReturn(
256 const TranslationID& id,
258 scoped_refptr<net::DrainableIOBuffer> buffer) {
259 DCHECK(thread_checker_.CalledOnValidThread());
260 pending_backend_operations_--;
261 PendingTranslationMap::iterator entry(pending_translations_.find(id));
262 if (entry == pending_translations_.end()) {
263 LOG(ERROR) << "OnCacheQueryReturn: id not found";
267 PendingTranslation* pt = &entry->second;
268 pt->got_cache_reply = true;
269 pt->got_cache_hit = (net_error == net::OK);
270 if (pt->got_cache_hit)
271 pt->nexe_read_buffer = buffer;
272 CheckCacheQueryReady(entry);
275 // Callback from temp file creation. |id| is bound from
276 // SendCacheQueryAndTempFileRequest, and fd is the created file descriptor.
277 // If there was an error, fd is kInvalidPlatformFileValue.
278 // (Bound callbacks must re-lookup the TranslationID because the translation
279 // could be cancelled before they get called).
280 void PnaclHost::OnTempFileReturn(const TranslationID& id,
281 base::PlatformFile fd) {
282 DCHECK(thread_checker_.CalledOnValidThread());
283 PendingTranslationMap::iterator entry(pending_translations_.find(id));
284 if (entry == pending_translations_.end()) {
285 // The renderer may have signaled an error or closed while the temp
286 // file was being created.
287 LOG(ERROR) << "OnTempFileReturn: id not found";
288 BrowserThread::PostBlockingPoolTask(
289 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
292 if (fd == base::kInvalidPlatformFileValue) {
293 // This translation will fail, but we need to retry any translation
294 // waiting for its result.
295 LOG(ERROR) << "OnTempFileReturn: temp file creation failed";
296 std::string key(entry->second.cache_key);
297 entry->second.callback.Run(fd, false);
298 bool may_be_cached = TranslationMayBeCached(entry);
299 pending_translations_.erase(entry);
300 // No translations will be waiting for entries that will not be stored.
302 RequeryMatchingTranslations(key);
305 PendingTranslation* pt = &entry->second;
306 pt->got_nexe_fd = true;
308 CheckCacheQueryReady(entry);
311 // Check whether both the cache query and the temp file have returned, and check
312 // whether we actually got a hit or not.
313 void PnaclHost::CheckCacheQueryReady(
314 const PendingTranslationMap::iterator& entry) {
315 PendingTranslation* pt = &entry->second;
316 if (!(pt->got_cache_reply && pt->got_nexe_fd))
318 if (!pt->got_cache_hit) {
319 // Check if there is already a pending translation for this file. If there
320 // is, we will wait for it to come back, to avoid redundant translations.
321 for (PendingTranslationMap::iterator it = pending_translations_.begin();
322 it != pending_translations_.end();
324 // Another translation matches if it's a request for the same file,
325 if (it->second.cache_key == entry->second.cache_key &&
326 // and it's not this translation,
327 it->first != entry->first &&
328 // and it can be stored in the cache,
329 TranslationMayBeCached(it) &&
330 // and it's already gotten past this check and returned the miss.
331 it->second.got_cache_reply &&
332 it->second.got_nexe_fd) {
340 if (!base::PostTaskAndReplyWithResult(
341 BrowserThread::GetBlockingPool(),
344 &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer),
345 base::Bind(&PnaclHost::OnBufferCopiedToTempFile,
346 weak_factory_.GetWeakPtr(),
348 pt->callback.Run(base::kInvalidPlatformFileValue, false);
352 //////////////////// GetNexeFd miss path
353 // Return the temp fd to the renderer, reporting a miss.
354 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
356 PendingTranslation* pt = &entry->second;
357 NexeFdCallback cb(pt->callback);
358 if (pt->nexe_fd == base::kInvalidPlatformFileValue) {
359 // Bad FD is unrecoverable, so clear out the entry
360 pending_translations_.erase(entry);
362 cb.Run(pt->nexe_fd, false);
365 // On error, just return a null refptr.
367 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
368 base::PlatformFile fd) {
369 base::PlatformFileInfo info;
370 scoped_refptr<net::DrainableIOBuffer> buffer;
372 if (!base::GetPlatformFileInfo(fd, &info) ||
373 info.size >= std::numeric_limits<int>::max()) {
374 PLOG(ERROR) << "GetPlatformFileInfo failed";
377 buffer = new net::DrainableIOBuffer(
378 new net::IOBuffer(static_cast<int>(info.size)), info.size);
379 if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) !=
381 PLOG(ERROR) << "CopyFileToBuffer file read failed";
388 base::ClosePlatformFile(fd);
392 // Called by the renderer in the miss path to report a finished translation
393 void PnaclHost::TranslationFinished(int render_process_id,
396 DCHECK(thread_checker_.CalledOnValidThread());
397 if (cache_state_ != CacheReady)
399 TranslationID id(render_process_id, pp_instance);
400 PendingTranslationMap::iterator entry(pending_translations_.find(id));
401 if (entry == pending_translations_.end()) {
402 LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id
403 << "," << pp_instance << " not found.";
406 bool store_nexe = true;
407 // If this is a premature response (i.e. we haven't returned a temp file
408 // yet) or if it's an unsuccessful translation, or if we are incognito,
409 // don't store in the cache.
410 // TODO(dschuff): use a separate in-memory cache for incognito
412 if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
413 !success || !TranslationMayBeCached(entry)) {
415 } else if (!base::PostTaskAndReplyWithResult(
416 BrowserThread::GetBlockingPool(),
418 base::Bind(&PnaclHost::CopyFileToBuffer,
419 entry->second.nexe_fd),
420 base::Bind(&PnaclHost::StoreTranslatedNexe,
421 weak_factory_.GetWeakPtr(),
427 // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
428 if (entry->second.got_nexe_fd) {
429 BrowserThread::PostBlockingPoolTask(
431 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
432 entry->second.nexe_fd));
434 pending_translations_.erase(entry);
438 // Store the translated nexe in the translation cache. Called back with the
439 // TranslationID from the host and the result of CopyFileToBuffer.
440 // (Bound callbacks must re-lookup the TranslationID because the translation
441 // could be cancelled before they get called).
442 void PnaclHost::StoreTranslatedNexe(
444 scoped_refptr<net::DrainableIOBuffer> buffer) {
445 DCHECK(thread_checker_.CalledOnValidThread());
446 if (cache_state_ != CacheReady)
448 PendingTranslationMap::iterator it(pending_translations_.find(id));
449 if (it == pending_translations_.end()) {
450 LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << ","
451 << id.second << " not found.";
455 if (buffer.get() == NULL) {
456 LOG(ERROR) << "Error reading translated nexe";
459 pending_backend_operations_++;
460 disk_cache_->StoreNexe(it->second.cache_key,
462 base::Bind(&PnaclHost::OnTranslatedNexeStored,
463 weak_factory_.GetWeakPtr(),
467 // After we know the nexe has been stored, we can clean up, and unblock any
468 // outstanding requests for the same file.
469 // (Bound callbacks must re-lookup the TranslationID because the translation
470 // could be cancelled before they get called).
471 void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) {
472 PendingTranslationMap::iterator entry(pending_translations_.find(id));
473 pending_backend_operations_--;
474 if (entry == pending_translations_.end()) {
475 // If the renderer closed while we were storing the nexe, we land here.
476 // Make sure we try to de-init.
480 std::string key(entry->second.cache_key);
481 pending_translations_.erase(entry);
482 RequeryMatchingTranslations(key);
485 // Check if any pending translations match |key|. If so, re-issue the cache
486 // query. In the overlapped miss case, we expect a hit this time, but a miss
487 // is also possible in case of an error.
488 void PnaclHost::RequeryMatchingTranslations(const std::string& key) {
489 // Check for outstanding misses to this same file
490 for (PendingTranslationMap::iterator it = pending_translations_.begin();
491 it != pending_translations_.end();
493 if (it->second.cache_key == key) {
494 // Re-send the cache read request. This time we expect a hit, but if
495 // something goes wrong, it will just handle it like a miss.
496 it->second.got_cache_reply = false;
497 pending_backend_operations_++;
498 disk_cache_->GetNexe(key,
499 base::Bind(&PnaclHost::OnCacheQueryReturn,
500 weak_factory_.GetWeakPtr(),
506 //////////////////// GetNexeFd hit path
509 int PnaclHost::CopyBufferToFile(base::PlatformFile fd,
510 scoped_refptr<net::DrainableIOBuffer> buffer) {
511 int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size());
513 PLOG(ERROR) << "CopyBufferToFile write error";
517 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
519 DCHECK(thread_checker_.CalledOnValidThread());
520 PendingTranslationMap::iterator entry(pending_translations_.find(id));
521 if (entry == pending_translations_.end()) {
524 if (file_error == -1) {
525 // Write error on the temp file. Request a new file and start over.
526 BrowserThread::PostBlockingPoolTask(
528 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
529 entry->second.nexe_fd));
530 entry->second.got_nexe_fd = false;
531 CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn,
532 weak_factory_.GetWeakPtr(),
536 base::PlatformFile fd = entry->second.nexe_fd;
537 entry->second.callback.Run(fd, true);
538 BrowserThread::PostBlockingPoolTask(
539 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
540 pending_translations_.erase(entry);
545 void PnaclHost::RendererClosing(int render_process_id) {
546 DCHECK(thread_checker_.CalledOnValidThread());
547 if (cache_state_ != CacheReady)
549 for (PendingTranslationMap::iterator it = pending_translations_.begin();
550 it != pending_translations_.end();) {
551 PendingTranslationMap::iterator to_erase(it++);
552 if (to_erase->first.first == render_process_id) {
553 // Clean up the open files.
554 BrowserThread::PostBlockingPoolTask(
556 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
557 to_erase->second.nexe_fd));
558 std::string key(to_erase->second.cache_key);
559 bool may_be_cached = TranslationMayBeCached(to_erase);
560 pending_translations_.erase(to_erase);
561 // No translations will be waiting for entries that will not be stored.
563 RequeryMatchingTranslations(key);
566 BrowserThread::PostTask(
569 base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
572 ////////////////// Cache data removal
573 void PnaclHost::ClearTranslationCacheEntriesBetween(
574 base::Time initial_time,
576 const base::Closure& callback) {
577 DCHECK(thread_checker_.CalledOnValidThread());
578 if (cache_state_ == CacheUninitialized) {
581 if (cache_state_ == CacheInitializing) {
582 // If the backend hasn't yet initialized, try the request again later.
583 BrowserThread::PostDelayedTask(
586 base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween,
587 weak_factory_.GetWeakPtr(),
591 base::TimeDelta::FromMilliseconds(
592 kTranslationCacheInitializationDelayMs));
595 pending_backend_operations_++;
596 int rv = disk_cache_->DoomEntriesBetween(
600 &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback));
601 if (rv != net::ERR_IO_PENDING)
602 OnEntriesDoomed(callback, rv);
605 void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) {
606 DCHECK(thread_checker_.CalledOnValidThread());
607 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback);
608 pending_backend_operations_--;
609 // When clearing the cache, the UI is blocked on all the cache-clearing
610 // operations, and freeing the backend actually blocks the IO thread. So
611 // instead of calling DeInitIfSafe directly, post it for later.
612 BrowserThread::PostTask(
615 base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
618 // Destroying the cache backend causes it to post tasks to the cache thread to
619 // flush to disk. Because PnaclHost is a singleton, it does not get destroyed
620 // until all the browser threads have gone away and it's too late to post
621 // anything (attempting to do so hangs shutdown). So we make sure to destroy it
622 // when we no longer have any outstanding operations that need it. These include
623 // pending translations, cache clear requests, and requests to read or write
624 // translated nexes. We check when renderers close, when cache clear requests
625 // finish, and when backend operations complete.
627 // It is not safe to delete the backend while it is initializing, nor if it has
628 // outstanding entry open requests; it is in theory safe to delete it with
629 // outstanding read/write requests, but because that distinction is hidden
630 // inside PnaclTranslationCache, we do not delete the backend if there are any
631 // backend requests in flight. As a last resort in the destructor, we just leak
632 // the backend to avoid hanging shutdown.
633 void PnaclHost::DeInitIfSafe() {
634 DCHECK(pending_backend_operations_ >= 0);
635 if (pending_translations_.empty() &&
636 pending_backend_operations_ <= 0 &&
637 cache_state_ == CacheReady) {
638 cache_state_ = CacheUninitialized;