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 "chrome/browser/nacl_host/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 "chrome/browser/nacl_host/pnacl_translation_cache.h"
15 #include "components/nacl/browser/nacl_browser.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;
29 PnaclHost::PnaclHost()
30 : pending_backend_operations_(0),
31 cache_state_(CacheUninitialized),
32 weak_factory_(this) {}
34 PnaclHost::~PnaclHost() {
35 // When PnaclHost is destroyed, it's too late to post anything to the cache
36 // thread (it will hang shutdown). So just leak the cache backend.
37 pnacl::PnaclTranslationCache* cache = disk_cache_.release();
41 PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); }
43 PnaclHost::PendingTranslation::PendingTranslation()
44 : process_handle(base::kNullProcessHandle),
46 nexe_fd(base::kInvalidPlatformFileValue),
48 got_cache_reply(false),
51 callback(NexeFdCallback()),
52 cache_info(nacl::PnaclCacheInfo()) {}
53 PnaclHost::PendingTranslation::~PendingTranslation() {}
55 bool PnaclHost::TranslationMayBeCached(
56 const PendingTranslationMap::iterator& entry) {
57 return !entry->second.is_incognito &&
58 !entry->second.cache_info.has_no_store_header;
61 /////////////////////////////////////// Initialization
63 static base::FilePath GetCachePath() {
64 NaClBrowserDelegate* browser_delegate = nacl::NaClBrowser::GetDelegate();
65 // Determine where the translation cache resides in the file system. It
66 // exists in Chrome's cache directory and is not tied to any specific
67 // profile. If we fail, return an empty path.
68 // Start by finding the user data directory.
69 base::FilePath user_data_dir;
70 if (!browser_delegate ||
71 !browser_delegate->GetUserDirectory(&user_data_dir)) {
72 return base::FilePath();
74 // The cache directory may or may not be the user data directory.
75 base::FilePath cache_file_path;
76 browser_delegate->GetCacheDirectory(&cache_file_path);
78 // Append the base file name to the cache directory.
79 return cache_file_path.Append(kTranslationCacheDirectoryName);
82 void PnaclHost::OnCacheInitialized(int net_error) {
83 DCHECK(thread_checker_.CalledOnValidThread());
84 // If the cache was cleared before the load completed, ignore.
85 if (cache_state_ == CacheReady)
87 if (net_error != net::OK) {
88 // This will cause the cache to attempt to re-init on the next call to
90 cache_state_ = CacheUninitialized;
92 cache_state_ = CacheReady;
96 void PnaclHost::Init() {
97 // Extra check that we're on the real IO thread since this version of
98 // Init isn't used in unit tests.
99 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
100 DCHECK(thread_checker_.CalledOnValidThread());
101 base::FilePath cache_path(GetCachePath());
102 if (cache_path.empty() || cache_state_ != CacheUninitialized)
104 disk_cache_.reset(new pnacl::PnaclTranslationCache());
105 cache_state_ = CacheInitializing;
106 int rv = disk_cache_->InitOnDisk(
108 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
109 if (rv != net::ERR_IO_PENDING)
110 OnCacheInitialized(rv);
113 // Initialize using the in-memory backend, and manually set the temporary file
114 // directory instead of using the system directory.
115 void PnaclHost::InitForTest(base::FilePath temp_dir) {
116 DCHECK(thread_checker_.CalledOnValidThread());
117 disk_cache_.reset(new pnacl::PnaclTranslationCache());
118 cache_state_ = CacheInitializing;
119 temp_dir_ = temp_dir;
120 int rv = disk_cache_->InitInMemory(
121 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
122 if (rv != net::ERR_IO_PENDING)
123 OnCacheInitialized(rv);
126 ///////////////////////////////////////// Temp files
128 // Create a temporary file on the blocking pool
130 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
131 TempFileCallback cb) {
132 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
134 base::FilePath file_path;
135 base::PlatformFile file_handle(base::kInvalidPlatformFileValue);
136 bool rv = temp_dir.empty()
137 ? file_util::CreateTemporaryFile(&file_path)
138 : file_util::CreateTemporaryFileInDir(temp_dir, &file_path);
140 PLOG(ERROR) << "Temp file creation failed.";
142 base::PlatformFileError error;
143 file_handle = base::CreatePlatformFile(
145 base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ |
146 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY |
147 base::PLATFORM_FILE_DELETE_ON_CLOSE,
151 if (error != base::PLATFORM_FILE_OK) {
152 PLOG(ERROR) << "Temp file open failed: " << error;
153 file_handle = base::kInvalidPlatformFileValue;
156 BrowserThread::PostTask(
157 BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle));
160 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
161 if (!BrowserThread::PostBlockingPoolSequencedTask(
162 "PnaclHostCreateTempFile",
164 base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
165 DCHECK(thread_checker_.CalledOnValidThread());
166 cb.Run(base::kInvalidPlatformFileValue);
170 ///////////////////////////////////////// GetNexeFd implementation
171 ////////////////////// Common steps
173 void PnaclHost::GetNexeFd(int render_process_id,
177 const nacl::PnaclCacheInfo& cache_info,
178 const NexeFdCallback& cb) {
179 DCHECK(thread_checker_.CalledOnValidThread());
180 if (cache_state_ == CacheUninitialized) {
183 if (cache_state_ != CacheReady) {
184 // If the backend hasn't yet initialized, try the request again later.
185 BrowserThread::PostDelayedTask(BrowserThread::IO,
187 base::Bind(&PnaclHost::GetNexeFd,
188 weak_factory_.GetWeakPtr(),
195 base::TimeDelta::FromMilliseconds(
196 kTranslationCacheInitializationDelayMs));
200 TranslationID id(render_process_id, pp_instance);
201 PendingTranslationMap::iterator entry = pending_translations_.find(id);
202 if (entry != pending_translations_.end()) {
203 // Existing translation must have been abandonded. Clean it up.
204 LOG(ERROR) << "GetNexeFd for already-pending translation";
205 pending_translations_.erase(entry);
208 std::string cache_key(disk_cache_->GetKey(cache_info));
209 if (cache_key.empty()) {
210 LOG(ERROR) << "GetNexeFd: Invalid cache info";
211 cb.Run(base::kInvalidPlatformFileValue, false);
215 PendingTranslation pt;
216 pt.render_view_id = render_view_id;
218 pt.cache_info = cache_info;
219 pt.cache_key = cache_key;
220 pt.is_incognito = is_incognito;
221 pending_translations_[id] = pt;
222 SendCacheQueryAndTempFileRequest(cache_key, id);
225 // Dispatch the cache read request and the temp file creation request
226 // simultaneously; currently we need a temp file regardless of whether the
228 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
229 const TranslationID& id) {
230 pending_backend_operations_++;
231 disk_cache_->GetNexe(
234 &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id));
237 base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id));
240 // Callback from the translation cache query. |id| is bound from
241 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
242 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
243 // by PnaclTranslationCache and now belongs to PnaclHost.
244 // (Bound callbacks must re-lookup the TranslationID because the translation
245 // could be cancelled before they get called).
246 void PnaclHost::OnCacheQueryReturn(
247 const TranslationID& id,
249 scoped_refptr<net::DrainableIOBuffer> buffer) {
250 DCHECK(thread_checker_.CalledOnValidThread());
251 pending_backend_operations_--;
252 PendingTranslationMap::iterator entry(pending_translations_.find(id));
253 if (entry == pending_translations_.end()) {
254 LOG(ERROR) << "OnCacheQueryReturn: id not found";
258 PendingTranslation* pt = &entry->second;
259 pt->got_cache_reply = true;
260 pt->got_cache_hit = (net_error == net::OK);
261 if (pt->got_cache_hit)
262 pt->nexe_read_buffer = buffer;
263 CheckCacheQueryReady(entry);
266 // Callback from temp file creation. |id| is bound from
267 // SendCacheQueryAndTempFileRequest, and fd is the created file descriptor.
268 // If there was an error, fd is kInvalidPlatformFileValue.
269 // (Bound callbacks must re-lookup the TranslationID because the translation
270 // could be cancelled before they get called).
271 void PnaclHost::OnTempFileReturn(const TranslationID& id,
272 base::PlatformFile fd) {
273 DCHECK(thread_checker_.CalledOnValidThread());
274 PendingTranslationMap::iterator entry(pending_translations_.find(id));
275 if (entry == pending_translations_.end()) {
276 // The renderer may have signaled an error or closed while the temp
277 // file was being created.
278 LOG(ERROR) << "OnTempFileReturn: id not found";
279 BrowserThread::PostBlockingPoolTask(
280 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
283 if (fd == base::kInvalidPlatformFileValue) {
284 // This translation will fail, but we need to retry any translation
285 // waiting for its result.
286 LOG(ERROR) << "OnTempFileReturn: temp file creation failed";
287 std::string key(entry->second.cache_key);
288 entry->second.callback.Run(fd, false);
289 bool may_be_cached = TranslationMayBeCached(entry);
290 pending_translations_.erase(entry);
291 // No translations will be waiting for entries that will not be stored.
293 RequeryMatchingTranslations(key);
296 PendingTranslation* pt = &entry->second;
297 pt->got_nexe_fd = true;
299 CheckCacheQueryReady(entry);
302 // Check whether both the cache query and the temp file have returned, and check
303 // whether we actually got a hit or not.
304 void PnaclHost::CheckCacheQueryReady(
305 const PendingTranslationMap::iterator& entry) {
306 PendingTranslation* pt = &entry->second;
307 if (!(pt->got_cache_reply && pt->got_nexe_fd))
309 if (!pt->got_cache_hit) {
310 // Check if there is already a pending translation for this file. If there
311 // is, we will wait for it to come back, to avoid redundant translations.
312 for (PendingTranslationMap::iterator it = pending_translations_.begin();
313 it != pending_translations_.end();
315 // Another translation matches if it's a request for the same file,
316 if (it->second.cache_key == entry->second.cache_key &&
317 // and it's not this translation,
318 it->first != entry->first &&
319 // and it can be stored in the cache,
320 TranslationMayBeCached(it) &&
321 // and it's already gotten past this check and returned the miss.
322 it->second.got_cache_reply &&
323 it->second.got_nexe_fd) {
331 if (!base::PostTaskAndReplyWithResult(
332 BrowserThread::GetBlockingPool(),
335 &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer),
336 base::Bind(&PnaclHost::OnBufferCopiedToTempFile,
337 weak_factory_.GetWeakPtr(),
339 pt->callback.Run(base::kInvalidPlatformFileValue, false);
343 //////////////////// GetNexeFd miss path
344 // Return the temp fd to the renderer, reporting a miss.
345 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
347 PendingTranslation* pt = &entry->second;
348 NexeFdCallback cb(pt->callback);
349 if (pt->nexe_fd == base::kInvalidPlatformFileValue) {
350 // Bad FD is unrecoverable, so clear out the entry
351 pending_translations_.erase(entry);
353 cb.Run(pt->nexe_fd, false);
356 // On error, just return a null refptr.
358 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
359 base::PlatformFile fd) {
360 base::PlatformFileInfo info;
361 scoped_refptr<net::DrainableIOBuffer> buffer;
363 if (!base::GetPlatformFileInfo(fd, &info) ||
364 info.size >= std::numeric_limits<int>::max()) {
365 PLOG(ERROR) << "GetPlatformFileInfo failed";
368 buffer = new net::DrainableIOBuffer(
369 new net::IOBuffer(static_cast<int>(info.size)), info.size);
370 if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) !=
372 PLOG(ERROR) << "CopyFileToBuffer file read failed";
379 base::ClosePlatformFile(fd);
383 // Called by the renderer in the miss path to report a finished translation
384 void PnaclHost::TranslationFinished(int render_process_id,
387 DCHECK(thread_checker_.CalledOnValidThread());
388 if (cache_state_ != CacheReady)
390 TranslationID id(render_process_id, pp_instance);
391 PendingTranslationMap::iterator entry(pending_translations_.find(id));
392 if (entry == pending_translations_.end()) {
393 LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id
394 << "," << pp_instance << " not found.";
397 bool store_nexe = true;
398 // If this is a premature response (i.e. we haven't returned a temp file
399 // yet) or if it's an unsuccessful translation, or if we are incognito,
400 // don't store in the cache.
401 // TODO(dschuff): use a separate in-memory cache for incognito
403 if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
404 !success || !TranslationMayBeCached(entry)) {
406 } else if (!base::PostTaskAndReplyWithResult(
407 BrowserThread::GetBlockingPool(),
409 base::Bind(&PnaclHost::CopyFileToBuffer,
410 entry->second.nexe_fd),
411 base::Bind(&PnaclHost::StoreTranslatedNexe,
412 weak_factory_.GetWeakPtr(),
418 // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
419 if (entry->second.got_nexe_fd) {
420 BrowserThread::PostBlockingPoolTask(
422 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
423 entry->second.nexe_fd));
425 pending_translations_.erase(entry);
429 // Store the translated nexe in the translation cache. Called back with the
430 // TranslationID from the host and the result of CopyFileToBuffer.
431 // (Bound callbacks must re-lookup the TranslationID because the translation
432 // could be cancelled before they get called).
433 void PnaclHost::StoreTranslatedNexe(
435 scoped_refptr<net::DrainableIOBuffer> buffer) {
436 DCHECK(thread_checker_.CalledOnValidThread());
437 if (cache_state_ != CacheReady)
439 PendingTranslationMap::iterator it(pending_translations_.find(id));
440 if (it == pending_translations_.end()) {
441 LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << ","
442 << id.second << " not found.";
446 if (buffer.get() == NULL) {
447 LOG(ERROR) << "Error reading translated nexe";
450 pending_backend_operations_++;
451 disk_cache_->StoreNexe(it->second.cache_key,
453 base::Bind(&PnaclHost::OnTranslatedNexeStored,
454 weak_factory_.GetWeakPtr(),
458 // After we know the nexe has been stored, we can clean up, and unblock any
459 // outstanding requests for the same file.
460 // (Bound callbacks must re-lookup the TranslationID because the translation
461 // could be cancelled before they get called).
462 void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) {
463 PendingTranslationMap::iterator entry(pending_translations_.find(id));
464 pending_backend_operations_--;
465 if (entry == pending_translations_.end()) {
466 // If the renderer closed while we were storing the nexe, we land here.
467 // Make sure we try to de-init.
471 std::string key(entry->second.cache_key);
472 pending_translations_.erase(entry);
473 RequeryMatchingTranslations(key);
476 // Check if any pending translations match |key|. If so, re-issue the cache
477 // query. In the overlapped miss case, we expect a hit this time, but a miss
478 // is also possible in case of an error.
479 void PnaclHost::RequeryMatchingTranslations(const std::string& key) {
480 // Check for outstanding misses to this same file
481 for (PendingTranslationMap::iterator it = pending_translations_.begin();
482 it != pending_translations_.end();
484 if (it->second.cache_key == key) {
485 // Re-send the cache read request. This time we expect a hit, but if
486 // something goes wrong, it will just handle it like a miss.
487 it->second.got_cache_reply = false;
488 pending_backend_operations_++;
489 disk_cache_->GetNexe(key,
490 base::Bind(&PnaclHost::OnCacheQueryReturn,
491 weak_factory_.GetWeakPtr(),
497 //////////////////// GetNexeFd hit path
500 int PnaclHost::CopyBufferToFile(base::PlatformFile fd,
501 scoped_refptr<net::DrainableIOBuffer> buffer) {
502 int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size());
504 PLOG(ERROR) << "CopyBufferToFile write error";
508 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
510 DCHECK(thread_checker_.CalledOnValidThread());
511 PendingTranslationMap::iterator entry(pending_translations_.find(id));
512 if (entry == pending_translations_.end()) {
515 if (file_error == -1) {
516 // Write error on the temp file. Request a new file and start over.
517 BrowserThread::PostBlockingPoolTask(
519 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
520 entry->second.nexe_fd));
521 entry->second.got_nexe_fd = false;
522 CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn,
523 weak_factory_.GetWeakPtr(),
527 base::PlatformFile fd = entry->second.nexe_fd;
528 entry->second.callback.Run(fd, true);
529 BrowserThread::PostBlockingPoolTask(
530 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
531 pending_translations_.erase(entry);
536 void PnaclHost::RendererClosing(int render_process_id) {
537 DCHECK(thread_checker_.CalledOnValidThread());
538 if (cache_state_ != CacheReady)
540 for (PendingTranslationMap::iterator it = pending_translations_.begin();
541 it != pending_translations_.end();) {
542 PendingTranslationMap::iterator to_erase(it++);
543 if (to_erase->first.first == render_process_id) {
544 // Clean up the open files.
545 BrowserThread::PostBlockingPoolTask(
547 base::Bind(base::IgnoreResult(base::ClosePlatformFile),
548 to_erase->second.nexe_fd));
549 std::string key(to_erase->second.cache_key);
550 bool may_be_cached = TranslationMayBeCached(to_erase);
551 pending_translations_.erase(to_erase);
552 // No translations will be waiting for entries that will not be stored.
554 RequeryMatchingTranslations(key);
557 BrowserThread::PostTask(
560 base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
563 ////////////////// Cache data removal
564 void PnaclHost::ClearTranslationCacheEntriesBetween(
565 base::Time initial_time,
567 const base::Closure& callback) {
568 DCHECK(thread_checker_.CalledOnValidThread());
569 if (cache_state_ == CacheUninitialized) {
572 if (cache_state_ == CacheInitializing) {
573 // If the backend hasn't yet initialized, try the request again later.
574 BrowserThread::PostDelayedTask(
577 base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween,
578 weak_factory_.GetWeakPtr(),
582 base::TimeDelta::FromMilliseconds(
583 kTranslationCacheInitializationDelayMs));
586 pending_backend_operations_++;
587 int rv = disk_cache_->DoomEntriesBetween(
591 &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback));
592 if (rv != net::ERR_IO_PENDING)
593 OnEntriesDoomed(callback, rv);
596 void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) {
597 DCHECK(thread_checker_.CalledOnValidThread());
598 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback);
599 pending_backend_operations_--;
600 // When clearing the cache, the UI is blocked on all the cache-clearing
601 // operations, and freeing the backend actually blocks the IO thread. So
602 // instead of calling DeInitIfSafe directly, post it for later.
603 BrowserThread::PostTask(
606 base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
609 // Destroying the cache backend causes it to post tasks to the cache thread to
610 // flush to disk. Because PnaclHost is a singleton, it does not get destroyed
611 // until all the browser threads have gone away and it's too late to post
612 // anything (attempting to do so hangs shutdown). So we make sure to destroy it
613 // when we no longer have any outstanding operations that need it. These include
614 // pending translations, cache clear requests, and requests to read or write
615 // translated nexes. We check when renderers close, when cache clear requests
616 // finish, and when backend operations complete.
618 // It is not safe to delete the backend while it is initializing, nor if it has
619 // outstanding entry open requests; it is in theory safe to delete it with
620 // outstanding read/write requests, but because that distinction is hidden
621 // inside PnaclTranslationCache, we do not delete the backend if there are any
622 // backend requests in flight. As a last resort in the destructor, we just leak
623 // the backend to avoid hanging shutdown.
624 void PnaclHost::DeInitIfSafe() {
625 DCHECK(pending_backend_operations_ >= 0);
626 if (pending_translations_.empty() && pending_backend_operations_ <= 0) {
627 cache_state_ = CacheUninitialized;