| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/nacl/browser/pnacl_translation_cache.h" |
| |
| #include <string.h> |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_checker.h" |
| #include "components/nacl/common/pnacl_types.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "third_party/icu/source/i18n/unicode/timezone.h" |
| |
| namespace pnacl { |
| |
| // This is in pnacl namespace instead of static so it can be used by the unit |
| // test. |
| constexpr int kMaxMemCacheSize = 100 * 1024 * 1024; |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Handle Reading/Writing to Cache. |
| |
| // PnaclTranslationCacheEntry is a shim that provides storage for the |
| // 'key' and 'data' strings as the disk_cache is performing various async |
| // operations. It also tracks the open disk_cache::Entry |
| // and ensures that the entry is closed. |
| class PnaclTranslationCacheEntry |
| : public base::RefCountedThreadSafe<PnaclTranslationCacheEntry> { |
| public: |
| static PnaclTranslationCacheEntry* GetReadEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| GetNexeCallback callback); |
| static PnaclTranslationCacheEntry* GetWriteEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| net::DrainableIOBuffer* write_nexe, |
| CompletionOnceCallback callback); |
| |
| PnaclTranslationCacheEntry(const PnaclTranslationCacheEntry&) = delete; |
| PnaclTranslationCacheEntry& operator=(const PnaclTranslationCacheEntry&) = |
| delete; |
| |
| void Start(); |
| |
| // Writes: --- |
| // v | |
| // Start -> Open Existing --------------> Write ---> Close |
| // \ ^ |
| // \ / |
| // --> Create -- |
| // Reads: |
| // Start -> Open --------Read ----> Close |
| // | ^ |
| // |__| |
| enum CacheStep { |
| UNINITIALIZED, |
| OPEN_ENTRY, |
| CREATE_ENTRY, |
| TRANSFER_ENTRY, |
| CLOSE_ENTRY, |
| FINISHED |
| }; |
| |
| private: |
| friend class base::RefCountedThreadSafe<PnaclTranslationCacheEntry>; |
| PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| bool is_read); |
| ~PnaclTranslationCacheEntry(); |
| |
| // Try to open an existing entry in the backend |
| void OpenEntry(); |
| // Create a new entry in the backend (for writes) |
| void CreateEntry(); |
| // Write |len| bytes to the backend, starting at |offset| |
| void WriteEntry(int offset, int len); |
| // Read |len| bytes from the backend, starting at |offset| |
| void ReadEntry(int offset, int len); |
| // If there was an error, doom the entry. Then post a task to the IO |
| // thread to close (and delete) it. |
| void CloseEntry(int rv); |
| // Call the user callback, and signal to the cache to delete this. |
| void Finish(int rv); |
| // Used as the callback for all operations to the backend except those that |
| // first open/create entries. Handle state transitions, track bytes |
| // transferred, and call the other helper methods. |
| void DispatchNext(int rv); |
| // Like above but for first opening or creating of |entry_|. |
| void SaveEntryAndDispatchNext(disk_cache::EntryResult result); |
| |
| base::WeakPtr<PnaclTranslationCache> cache_; |
| std::string key_; |
| raw_ptr<disk_cache::Entry> entry_; |
| CacheStep step_; |
| bool is_read_; |
| GetNexeCallback read_callback_; |
| CompletionOnceCallback write_callback_; |
| scoped_refptr<net::DrainableIOBuffer> io_buf_; |
| base::ThreadChecker thread_checker_; |
| }; |
| |
| // static |
| PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| GetNexeCallback callback) { |
| PnaclTranslationCacheEntry* entry( |
| new PnaclTranslationCacheEntry(cache, key, true)); |
| entry->read_callback_ = std::move(callback); |
| return entry; |
| } |
| |
| // static |
| PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| net::DrainableIOBuffer* write_nexe, |
| CompletionOnceCallback callback) { |
| PnaclTranslationCacheEntry* entry( |
| new PnaclTranslationCacheEntry(cache, key, false)); |
| entry->io_buf_ = write_nexe; |
| entry->write_callback_ = std::move(callback); |
| return entry; |
| } |
| |
| PnaclTranslationCacheEntry::PnaclTranslationCacheEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| bool is_read) |
| : cache_(cache), |
| key_(key), |
| entry_(nullptr), |
| step_(UNINITIALIZED), |
| is_read_(is_read) {} |
| |
| PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() { |
| // Ensure we have called the user's callback |
| if (step_ != FINISHED) { |
| if (!read_callback_.is_null()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(read_callback_), net::ERR_ABORTED, |
| scoped_refptr<net::DrainableIOBuffer>())); |
| } |
| if (!write_callback_.is_null()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(write_callback_), net::ERR_ABORTED)); |
| } |
| } |
| } |
| |
| void PnaclTranslationCacheEntry::Start() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| step_ = OPEN_ENTRY; |
| OpenEntry(); |
| } |
| |
| // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called |
| // from DispatchNext, so they know that cache_ is still valid. |
| void PnaclTranslationCacheEntry::OpenEntry() { |
| disk_cache::EntryResult result = cache_->backend()->OpenEntry( |
| key_, net::HIGHEST, |
| base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext, |
| this)); |
| if (result.net_error() != net::ERR_IO_PENDING) |
| SaveEntryAndDispatchNext(std::move(result)); |
| } |
| |
| void PnaclTranslationCacheEntry::CreateEntry() { |
| disk_cache::EntryResult result = cache_->backend()->CreateEntry( |
| key_, net::HIGHEST, |
| base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext, |
| this)); |
| if (result.net_error() != net::ERR_IO_PENDING) |
| SaveEntryAndDispatchNext(std::move(result)); |
| } |
| |
| void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) { |
| DCHECK(io_buf_->BytesRemaining() == len); |
| int rv = entry_->WriteData( |
| 1, offset, io_buf_.get(), len, |
| base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this), false); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) { |
| int rv = entry_->ReadData( |
| 1, offset, io_buf_.get(), len, |
| base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this)); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::CloseEntry(int rv) { |
| DCHECK(entry_); |
| if (rv < 0) { |
| LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv); |
| entry_->Doom(); |
| } |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&disk_cache::Entry::Close, base::Unretained(entry_))); |
| Finish(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::Finish(int rv) { |
| step_ = FINISHED; |
| if (is_read_) { |
| if (!read_callback_.is_null()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(read_callback_), rv, io_buf_)); |
| } |
| } else { |
| if (!write_callback_.is_null()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(write_callback_), rv)); |
| } |
| } |
| cache_->OpComplete(this); |
| } |
| |
| void PnaclTranslationCacheEntry::DispatchNext(int rv) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!cache_) |
| return; |
| |
| switch (step_) { |
| case UNINITIALIZED: |
| case FINISHED: |
| LOG(ERROR) << "DispatchNext called uninitialized"; |
| break; |
| |
| case OPEN_ENTRY: |
| if (rv == net::OK) { |
| step_ = TRANSFER_ENTRY; |
| if (is_read_) { |
| int bytes_to_transfer = entry_->GetDataSize(1); |
| io_buf_ = base::MakeRefCounted<net::DrainableIOBuffer>( |
| base::MakeRefCounted<net::IOBufferWithSize>(bytes_to_transfer), |
| bytes_to_transfer); |
| ReadEntry(0, bytes_to_transfer); |
| } else { |
| WriteEntry(0, io_buf_->size()); |
| } |
| } else { |
| if (rv != net::ERR_FAILED) { |
| // ERROR_FAILED is what we expect if the entry doesn't exist. |
| LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv); |
| } |
| if (is_read_) { |
| // Just a cache miss, not necessarily an error. |
| entry_ = nullptr; |
| Finish(rv); |
| } else { |
| step_ = CREATE_ENTRY; |
| CreateEntry(); |
| } |
| } |
| break; |
| |
| case CREATE_ENTRY: |
| if (rv == net::OK) { |
| step_ = TRANSFER_ENTRY; |
| WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); |
| } else { |
| LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv); |
| Finish(rv); |
| } |
| break; |
| |
| case TRANSFER_ENTRY: |
| if (rv < 0) { |
| // We do not call DispatchNext directly if WriteEntry/ReadEntry returns |
| // ERR_IO_PENDING, and the callback should not return that value either. |
| LOG(ERROR) << "Failed to complete write to entry: " |
| << net::ErrorToString(rv); |
| step_ = CLOSE_ENTRY; |
| CloseEntry(rv); |
| break; |
| } else if (rv > 0) { |
| io_buf_->DidConsume(rv); |
| if (io_buf_->BytesRemaining() > 0) { |
| if (is_read_) { |
| ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); |
| } else { |
| WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); |
| } |
| break; |
| } |
| } |
| // rv == 0 or we fell through (i.e. we have transferred all the bytes) |
| step_ = CLOSE_ENTRY; |
| DCHECK(io_buf_->BytesConsumed() == io_buf_->size()); |
| if (is_read_) |
| io_buf_->SetOffset(0); |
| CloseEntry(0); |
| break; |
| |
| case CLOSE_ENTRY: |
| step_ = UNINITIALIZED; |
| break; |
| } |
| } |
| |
| void PnaclTranslationCacheEntry::SaveEntryAndDispatchNext( |
| disk_cache::EntryResult result) { |
| int rv = result.net_error(); |
| entry_ = result.ReleaseEntry(); |
| DispatchNext(rv); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) { |
| open_entries_.erase(entry); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Construction and cache backend initialization |
| PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {} |
| |
| PnaclTranslationCache::~PnaclTranslationCache() {} |
| |
| int PnaclTranslationCache::Init(net::CacheType cache_type, |
| const base::FilePath& cache_dir, |
| int cache_size, |
| CompletionOnceCallback callback) { |
| disk_cache::BackendResult result = disk_cache::CreateCacheBackend( |
| cache_type, net::CACHE_BACKEND_DEFAULT, /*file_operations=*/nullptr, |
| cache_dir, cache_size, disk_cache::ResetHandling::kResetOnError, |
| nullptr, /* dummy net log */ |
| base::BindOnce(&PnaclTranslationCache::OnCreateBackendComplete, |
| AsWeakPtr())); |
| if (result.net_error == net::OK) { |
| disk_cache_ = std::move(result.backend); |
| } else if (result.net_error == net::ERR_IO_PENDING) { |
| init_callback_ = std::move(callback); |
| } |
| return result.net_error; |
| } |
| |
| void PnaclTranslationCache::OnCreateBackendComplete( |
| disk_cache::BackendResult result) { |
| if (result.net_error < 0) { |
| LOG(ERROR) << "Backend init failed:" |
| << net::ErrorToString(result.net_error); |
| } |
| disk_cache_ = std::move(result.backend); |
| // Invoke our client's callback function. |
| if (!init_callback_.is_null()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(init_callback_), result.net_error)); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // High-level API |
| |
| void PnaclTranslationCache::StoreNexe(const std::string& key, |
| net::DrainableIOBuffer* nexe_data, |
| CompletionOnceCallback callback) { |
| PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry( |
| AsWeakPtr(), key, nexe_data, std::move(callback)); |
| open_entries_[entry] = entry; |
| entry->Start(); |
| } |
| |
| void PnaclTranslationCache::GetNexe(const std::string& key, |
| GetNexeCallback callback) { |
| PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetReadEntry( |
| AsWeakPtr(), key, std::move(callback)); |
| open_entries_[entry] = entry; |
| entry->Start(); |
| } |
| |
| int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory, |
| CompletionOnceCallback callback) { |
| in_memory_ = false; |
| return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */, |
| std::move(callback)); |
| } |
| |
| int PnaclTranslationCache::InitInMemory(CompletionOnceCallback callback) { |
| in_memory_ = true; |
| return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, |
| std::move(callback)); |
| } |
| |
| int PnaclTranslationCache::Size() { |
| return disk_cache_ ? disk_cache_->GetEntryCount() : -1; |
| } |
| |
| // Beware that any changes to this function or to PnaclCacheInfo will |
| // effectively invalidate existing translation cache entries. |
| |
| // static |
| std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) { |
| if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0 || |
| info.extra_flags.size() > 512) { |
| return std::string(); |
| } |
| |
| // Filter the username, password, and ref components from the URL. |
| GURL::Replacements replacements; |
| replacements.ClearUsername(); |
| replacements.ClearPassword(); |
| replacements.ClearRef(); |
| const GURL key_url = info.pexe_url.ReplaceComponents(replacements); |
| |
| const std::string timestamp = base::UnlocalizedTimeFormatWithPattern( |
| info.last_modified, "y:M:d:H:m:s:S:'UTC'", icu::TimeZone::getGMT()); |
| |
| return base::StringPrintf( |
| "ABI:%d;opt:%d%s;URL:%s;modified:%s;etag:%s;sandbox:%s;extra_flags:%s;", |
| info.abi_version, info.opt_level, info.use_subzero ? "subzero" : "", |
| key_url.spec().c_str(), timestamp.c_str(), info.etag.c_str(), |
| info.sandbox_isa.c_str(), info.extra_flags.c_str()); |
| } |
| |
| int PnaclTranslationCache::DoomEntriesBetween(base::Time initial, |
| base::Time end, |
| CompletionOnceCallback callback) { |
| return disk_cache_->DoomEntriesBetween(initial, end, std::move(callback)); |
| } |
| |
| } // namespace pnacl |