xref: /aosp_15_r20/external/cronet/components/nacl/browser/pnacl_translation_cache.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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_translation_cache.h"
6 
7 #include <string.h>
8 
9 #include <string>
10 #include <utility>
11 
12 #include "base/files/file_path.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback.h"
15 #include "base/i18n/time_formatting.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/thread_checker.h"
20 #include "components/nacl/common/pnacl_types.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 #include "net/disk_cache/disk_cache.h"
26 #include "third_party/icu/source/i18n/unicode/timezone.h"
27 
28 namespace pnacl {
29 
30 // This is in pnacl namespace instead of static so it can be used by the unit
31 // test.
32 constexpr int kMaxMemCacheSize = 100 * 1024 * 1024;
33 
34 //////////////////////////////////////////////////////////////////////
35 // Handle Reading/Writing to Cache.
36 
37 // PnaclTranslationCacheEntry is a shim that provides storage for the
38 // 'key' and 'data' strings as the disk_cache is performing various async
39 // operations. It also tracks the open disk_cache::Entry
40 // and ensures that the entry is closed.
41 class PnaclTranslationCacheEntry
42     : public base::RefCountedThreadSafe<PnaclTranslationCacheEntry> {
43  public:
44   static PnaclTranslationCacheEntry* GetReadEntry(
45       base::WeakPtr<PnaclTranslationCache> cache,
46       const std::string& key,
47       GetNexeCallback callback);
48   static PnaclTranslationCacheEntry* GetWriteEntry(
49       base::WeakPtr<PnaclTranslationCache> cache,
50       const std::string& key,
51       net::DrainableIOBuffer* write_nexe,
52       CompletionOnceCallback callback);
53 
54   PnaclTranslationCacheEntry(const PnaclTranslationCacheEntry&) = delete;
55   PnaclTranslationCacheEntry& operator=(const PnaclTranslationCacheEntry&) =
56       delete;
57 
58   void Start();
59 
60   // Writes:                                ---
61   //                                        v  |
62   // Start -> Open Existing --------------> Write ---> Close
63   //                          \              ^
64   //                           \             /
65   //                            --> Create --
66   // Reads:
67   // Start -> Open --------Read ----> Close
68   //                       |  ^
69   //                       |__|
70   enum CacheStep {
71     UNINITIALIZED,
72     OPEN_ENTRY,
73     CREATE_ENTRY,
74     TRANSFER_ENTRY,
75     CLOSE_ENTRY,
76     FINISHED
77   };
78 
79  private:
80   friend class base::RefCountedThreadSafe<PnaclTranslationCacheEntry>;
81   PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,
82                              const std::string& key,
83                              bool is_read);
84   ~PnaclTranslationCacheEntry();
85 
86   // Try to open an existing entry in the backend
87   void OpenEntry();
88   // Create a new entry in the backend (for writes)
89   void CreateEntry();
90   // Write |len| bytes to the backend, starting at |offset|
91   void WriteEntry(int offset, int len);
92   // Read |len| bytes from the backend, starting at |offset|
93   void ReadEntry(int offset, int len);
94   // If there was an error, doom the entry. Then post a task to the IO
95   // thread to close (and delete) it.
96   void CloseEntry(int rv);
97   // Call the user callback, and signal to the cache to delete this.
98   void Finish(int rv);
99   // Used as the callback for all operations to the backend except those that
100   // first open/create entries. Handle state transitions, track bytes
101   // transferred, and call the other helper methods.
102   void DispatchNext(int rv);
103   // Like above but for first opening or creating of |entry_|.
104   void SaveEntryAndDispatchNext(disk_cache::EntryResult result);
105 
106   base::WeakPtr<PnaclTranslationCache> cache_;
107   std::string key_;
108   raw_ptr<disk_cache::Entry> entry_;
109   CacheStep step_;
110   bool is_read_;
111   GetNexeCallback read_callback_;
112   CompletionOnceCallback write_callback_;
113   scoped_refptr<net::DrainableIOBuffer> io_buf_;
114   base::ThreadChecker thread_checker_;
115 };
116 
117 // static
GetReadEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,GetNexeCallback callback)118 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry(
119     base::WeakPtr<PnaclTranslationCache> cache,
120     const std::string& key,
121     GetNexeCallback callback) {
122   PnaclTranslationCacheEntry* entry(
123       new PnaclTranslationCacheEntry(cache, key, true));
124   entry->read_callback_ = std::move(callback);
125   return entry;
126 }
127 
128 // static
GetWriteEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,net::DrainableIOBuffer * write_nexe,CompletionOnceCallback callback)129 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry(
130     base::WeakPtr<PnaclTranslationCache> cache,
131     const std::string& key,
132     net::DrainableIOBuffer* write_nexe,
133     CompletionOnceCallback callback) {
134   PnaclTranslationCacheEntry* entry(
135       new PnaclTranslationCacheEntry(cache, key, false));
136   entry->io_buf_ = write_nexe;
137   entry->write_callback_ = std::move(callback);
138   return entry;
139 }
140 
PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,const std::string & key,bool is_read)141 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
142     base::WeakPtr<PnaclTranslationCache> cache,
143     const std::string& key,
144     bool is_read)
145     : cache_(cache),
146       key_(key),
147       entry_(nullptr),
148       step_(UNINITIALIZED),
149       is_read_(is_read) {}
150 
~PnaclTranslationCacheEntry()151 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
152   // Ensure we have called the user's callback
153   if (step_ != FINISHED) {
154     if (!read_callback_.is_null()) {
155       content::GetUIThreadTaskRunner({})->PostTask(
156           FROM_HERE, base::BindOnce(std::move(read_callback_), net::ERR_ABORTED,
157                                     scoped_refptr<net::DrainableIOBuffer>()));
158     }
159     if (!write_callback_.is_null()) {
160       content::GetUIThreadTaskRunner({})->PostTask(
161           FROM_HERE,
162           base::BindOnce(std::move(write_callback_), net::ERR_ABORTED));
163     }
164   }
165 }
166 
Start()167 void PnaclTranslationCacheEntry::Start() {
168   DCHECK(thread_checker_.CalledOnValidThread());
169   step_ = OPEN_ENTRY;
170   OpenEntry();
171 }
172 
173 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called
174 // from DispatchNext, so they know that cache_ is still valid.
OpenEntry()175 void PnaclTranslationCacheEntry::OpenEntry() {
176   disk_cache::EntryResult result = cache_->backend()->OpenEntry(
177       key_, net::HIGHEST,
178       base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext,
179                      this));
180   if (result.net_error() != net::ERR_IO_PENDING)
181     SaveEntryAndDispatchNext(std::move(result));
182 }
183 
CreateEntry()184 void PnaclTranslationCacheEntry::CreateEntry() {
185   disk_cache::EntryResult result = cache_->backend()->CreateEntry(
186       key_, net::HIGHEST,
187       base::BindOnce(&PnaclTranslationCacheEntry::SaveEntryAndDispatchNext,
188                      this));
189   if (result.net_error() != net::ERR_IO_PENDING)
190     SaveEntryAndDispatchNext(std::move(result));
191 }
192 
WriteEntry(int offset,int len)193 void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) {
194   DCHECK(io_buf_->BytesRemaining() == len);
195   int rv = entry_->WriteData(
196       1, offset, io_buf_.get(), len,
197       base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this), false);
198   if (rv != net::ERR_IO_PENDING)
199     DispatchNext(rv);
200 }
201 
ReadEntry(int offset,int len)202 void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) {
203   int rv = entry_->ReadData(
204       1, offset, io_buf_.get(), len,
205       base::BindOnce(&PnaclTranslationCacheEntry::DispatchNext, this));
206   if (rv != net::ERR_IO_PENDING)
207     DispatchNext(rv);
208 }
209 
CloseEntry(int rv)210 void PnaclTranslationCacheEntry::CloseEntry(int rv) {
211   DCHECK(entry_);
212   if (rv < 0) {
213     LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv);
214     entry_->Doom();
215   }
216   content::GetUIThreadTaskRunner({})->PostTask(
217       FROM_HERE,
218       base::BindOnce(&disk_cache::Entry::Close, base::Unretained(entry_)));
219   Finish(rv);
220 }
221 
Finish(int rv)222 void PnaclTranslationCacheEntry::Finish(int rv) {
223   step_ = FINISHED;
224   if (is_read_) {
225     if (!read_callback_.is_null()) {
226       content::GetUIThreadTaskRunner({})->PostTask(
227           FROM_HERE, base::BindOnce(std::move(read_callback_), rv, io_buf_));
228     }
229   } else {
230     if (!write_callback_.is_null()) {
231       content::GetUIThreadTaskRunner({})->PostTask(
232           FROM_HERE, base::BindOnce(std::move(write_callback_), rv));
233     }
234   }
235   cache_->OpComplete(this);
236 }
237 
DispatchNext(int rv)238 void PnaclTranslationCacheEntry::DispatchNext(int rv) {
239   DCHECK(thread_checker_.CalledOnValidThread());
240   if (!cache_)
241     return;
242 
243   switch (step_) {
244     case UNINITIALIZED:
245     case FINISHED:
246       LOG(ERROR) << "DispatchNext called uninitialized";
247       break;
248 
249     case OPEN_ENTRY:
250       if (rv == net::OK) {
251         step_ = TRANSFER_ENTRY;
252         if (is_read_) {
253           int bytes_to_transfer = entry_->GetDataSize(1);
254           io_buf_ = base::MakeRefCounted<net::DrainableIOBuffer>(
255               base::MakeRefCounted<net::IOBufferWithSize>(bytes_to_transfer),
256               bytes_to_transfer);
257           ReadEntry(0, bytes_to_transfer);
258         } else {
259           WriteEntry(0, io_buf_->size());
260         }
261       } else {
262         if (rv != net::ERR_FAILED) {
263           // ERROR_FAILED is what we expect if the entry doesn't exist.
264           LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv);
265         }
266         if (is_read_) {
267           // Just a cache miss, not necessarily an error.
268           entry_ = nullptr;
269           Finish(rv);
270         } else {
271           step_ = CREATE_ENTRY;
272           CreateEntry();
273         }
274       }
275       break;
276 
277     case CREATE_ENTRY:
278       if (rv == net::OK) {
279         step_ = TRANSFER_ENTRY;
280         WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
281       } else {
282         LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv);
283         Finish(rv);
284       }
285       break;
286 
287     case TRANSFER_ENTRY:
288       if (rv < 0) {
289         // We do not call DispatchNext directly if WriteEntry/ReadEntry returns
290         // ERR_IO_PENDING, and the callback should not return that value either.
291         LOG(ERROR) << "Failed to complete write to entry: "
292                    << net::ErrorToString(rv);
293         step_ = CLOSE_ENTRY;
294         CloseEntry(rv);
295         break;
296       } else if (rv > 0) {
297         io_buf_->DidConsume(rv);
298         if (io_buf_->BytesRemaining() > 0) {
299           if (is_read_) {
300             ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
301           } else {
302             WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
303           }
304           break;
305         }
306       }
307       // rv == 0 or we fell through (i.e. we have transferred all the bytes)
308       step_ = CLOSE_ENTRY;
309       DCHECK(io_buf_->BytesConsumed() == io_buf_->size());
310       if (is_read_)
311         io_buf_->SetOffset(0);
312       CloseEntry(0);
313       break;
314 
315     case CLOSE_ENTRY:
316       step_ = UNINITIALIZED;
317       break;
318   }
319 }
320 
SaveEntryAndDispatchNext(disk_cache::EntryResult result)321 void PnaclTranslationCacheEntry::SaveEntryAndDispatchNext(
322     disk_cache::EntryResult result) {
323   int rv = result.net_error();
324   entry_ = result.ReleaseEntry();
325   DispatchNext(rv);
326 }
327 
328 //////////////////////////////////////////////////////////////////////
OpComplete(PnaclTranslationCacheEntry * entry)329 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
330   open_entries_.erase(entry);
331 }
332 
333 //////////////////////////////////////////////////////////////////////
334 // Construction and cache backend initialization
PnaclTranslationCache()335 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
336 
~PnaclTranslationCache()337 PnaclTranslationCache::~PnaclTranslationCache() {}
338 
Init(net::CacheType cache_type,const base::FilePath & cache_dir,int cache_size,CompletionOnceCallback callback)339 int PnaclTranslationCache::Init(net::CacheType cache_type,
340                                 const base::FilePath& cache_dir,
341                                 int cache_size,
342                                 CompletionOnceCallback callback) {
343   disk_cache::BackendResult result = disk_cache::CreateCacheBackend(
344       cache_type, net::CACHE_BACKEND_DEFAULT, /*file_operations=*/nullptr,
345       cache_dir, cache_size, disk_cache::ResetHandling::kResetOnError,
346       nullptr, /* dummy net log */
347       base::BindOnce(&PnaclTranslationCache::OnCreateBackendComplete,
348                      weak_ptr_factory_.GetWeakPtr()));
349   if (result.net_error == net::OK) {
350     disk_cache_ = std::move(result.backend);
351   } else if (result.net_error == net::ERR_IO_PENDING) {
352     init_callback_ = std::move(callback);
353   }
354   return result.net_error;
355 }
356 
OnCreateBackendComplete(disk_cache::BackendResult result)357 void PnaclTranslationCache::OnCreateBackendComplete(
358     disk_cache::BackendResult result) {
359   if (result.net_error < 0) {
360     LOG(ERROR) << "Backend init failed:"
361                << net::ErrorToString(result.net_error);
362   }
363   disk_cache_ = std::move(result.backend);
364   // Invoke our client's callback function.
365   if (!init_callback_.is_null()) {
366     content::GetUIThreadTaskRunner({})->PostTask(
367         FROM_HERE, base::BindOnce(std::move(init_callback_), result.net_error));
368   }
369 }
370 
371 //////////////////////////////////////////////////////////////////////
372 // High-level API
373 
StoreNexe(const std::string & key,net::DrainableIOBuffer * nexe_data,CompletionOnceCallback callback)374 void PnaclTranslationCache::StoreNexe(const std::string& key,
375                                       net::DrainableIOBuffer* nexe_data,
376                                       CompletionOnceCallback callback) {
377   PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry(
378       weak_ptr_factory_.GetWeakPtr(), key, nexe_data, std::move(callback));
379   open_entries_[entry] = entry;
380   entry->Start();
381 }
382 
GetNexe(const std::string & key,GetNexeCallback callback)383 void PnaclTranslationCache::GetNexe(const std::string& key,
384                                     GetNexeCallback callback) {
385   PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetReadEntry(
386       weak_ptr_factory_.GetWeakPtr(), key, std::move(callback));
387   open_entries_[entry] = entry;
388   entry->Start();
389 }
390 
InitOnDisk(const base::FilePath & cache_directory,CompletionOnceCallback callback)391 int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory,
392                                       CompletionOnceCallback callback) {
393   in_memory_ = false;
394   return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */,
395               std::move(callback));
396 }
397 
InitInMemory(CompletionOnceCallback callback)398 int PnaclTranslationCache::InitInMemory(CompletionOnceCallback callback) {
399   in_memory_ = true;
400   return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize,
401               std::move(callback));
402 }
403 
Size()404 int PnaclTranslationCache::Size() {
405   return disk_cache_ ? disk_cache_->GetEntryCount() : -1;
406 }
407 
408 // Beware that any changes to this function or to PnaclCacheInfo will
409 // effectively invalidate existing translation cache entries.
410 
411 // static
GetKey(const nacl::PnaclCacheInfo & info)412 std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) {
413   if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0 ||
414       info.extra_flags.size() > 512) {
415     return std::string();
416   }
417 
418   // Filter the username, password, and ref components from the URL.
419   GURL::Replacements replacements;
420   replacements.ClearUsername();
421   replacements.ClearPassword();
422   replacements.ClearRef();
423   const GURL key_url = info.pexe_url.ReplaceComponents(replacements);
424 
425   const std::string timestamp = base::UnlocalizedTimeFormatWithPattern(
426       info.last_modified, "y:M:d:H:m:s:S:'UTC'", icu::TimeZone::getGMT());
427 
428   return base::StringPrintf(
429       "ABI:%d;opt:%d%s;URL:%s;modified:%s;etag:%s;sandbox:%s;extra_flags:%s;",
430       info.abi_version, info.opt_level, info.use_subzero ? "subzero" : "",
431       key_url.spec().c_str(), timestamp.c_str(), info.etag.c_str(),
432       info.sandbox_isa.c_str(), info.extra_flags.c_str());
433 }
434 
DoomEntriesBetween(base::Time initial,base::Time end,CompletionOnceCallback callback)435 int PnaclTranslationCache::DoomEntriesBetween(base::Time initial,
436                                               base::Time end,
437                                               CompletionOnceCallback callback) {
438   return disk_cache_->DoomEntriesBetween(initial, end, std::move(callback));
439 }
440 
441 }  // namespace pnacl
442