1 // Copyright (c) 2012 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.
4 
5 #include "content/browser/appcache/appcache_disk_cache.h"
6 
7 #include <limits>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/check.h"
13 #include "base/files/file_path.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "net/base/cache_type.h"
19 #include "net/base/completion_repeating_callback.h"
20 #include "net/base/net_errors.h"
21 
22 namespace content {
23 
24 // A callback shim that provides storage for the 'backend_ptr' value
25 // and will delete a resulting ptr if completion occurs after its
26 // been canceled.
27 class AppCacheDiskCache::CreateBackendCallbackShim
28     : public base::RefCounted<CreateBackendCallbackShim> {
29  public:
CreateBackendCallbackShim(AppCacheDiskCache * object)30   explicit CreateBackendCallbackShim(AppCacheDiskCache* object)
31       : appcache_diskcache_(object) {
32   }
33 
Cancel()34   void Cancel() { appcache_diskcache_ = nullptr; }
35 
Callback(int return_value)36   void Callback(int return_value) {
37     if (appcache_diskcache_)
38       appcache_diskcache_->OnCreateBackendComplete(return_value);
39   }
40 
41   std::unique_ptr<disk_cache::Backend> backend_ptr_;  // Accessed directly.
42 
43  private:
44   friend class base::RefCounted<CreateBackendCallbackShim>;
45 
46   ~CreateBackendCallbackShim() = default;
47 
48   AppCacheDiskCache* appcache_diskcache_;  // Unowned pointer.
49 };
50 
AppCacheDiskCacheEntry(disk_cache::Entry * disk_cache_entry,AppCacheDiskCache * cache)51 AppCacheDiskCacheEntry::AppCacheDiskCacheEntry(
52     disk_cache::Entry* disk_cache_entry,
53     AppCacheDiskCache* cache)
54     : disk_cache_entry_(disk_cache_entry), cache_(cache) {
55   DCHECK(disk_cache_entry);
56   DCHECK(cache);
57   cache_->AddOpenEntry(this);
58 }
59 
~AppCacheDiskCacheEntry()60 AppCacheDiskCacheEntry::~AppCacheDiskCacheEntry() {
61   if (cache_)
62     cache_->RemoveOpenEntry(this);
63 }
64 
Read(int index,int64_t offset,net::IOBuffer * buf,int buf_len,net::CompletionOnceCallback callback)65 int AppCacheDiskCacheEntry::Read(int index,
66                                  int64_t offset,
67                                  net::IOBuffer* buf,
68                                  int buf_len,
69                                  net::CompletionOnceCallback callback) {
70   if (offset < 0 || offset > std::numeric_limits<int32_t>::max())
71     return net::ERR_INVALID_ARGUMENT;
72   if (!disk_cache_entry_)
73     return net::ERR_ABORTED;
74   return disk_cache_entry_->ReadData(index, static_cast<int>(offset), buf,
75                                      buf_len, std::move(callback));
76 }
77 
Write(int index,int64_t offset,net::IOBuffer * buf,int buf_len,net::CompletionOnceCallback callback)78 int AppCacheDiskCacheEntry::Write(int index,
79                                   int64_t offset,
80                                   net::IOBuffer* buf,
81                                   int buf_len,
82                                   net::CompletionOnceCallback callback) {
83   if (offset < 0 || offset > std::numeric_limits<int32_t>::max())
84     return net::ERR_INVALID_ARGUMENT;
85   if (!disk_cache_entry_)
86     return net::ERR_ABORTED;
87   const bool kTruncate = true;
88   return disk_cache_entry_->WriteData(index, static_cast<int>(offset), buf,
89                                       buf_len, std::move(callback), kTruncate);
90 }
91 
GetSize(int index)92 int64_t AppCacheDiskCacheEntry::GetSize(int index) {
93   return disk_cache_entry_ ? disk_cache_entry_->GetDataSize(index) : 0L;
94 }
95 
Close()96 void AppCacheDiskCacheEntry::Close() {
97   if (disk_cache_entry_)
98     disk_cache_entry_->Close();
99   delete this;
100 }
101 
Abandon()102 void AppCacheDiskCacheEntry::Abandon() {
103   cache_ = nullptr;
104   disk_cache_entry_->Close();
105   disk_cache_entry_ = nullptr;
106 }
107 
108 namespace {
109 
110 // Separate object to hold state for each Create, Delete, or Doom call
111 // while the call is in-flight and to produce an EntryImpl upon completion.
112 class ActiveCall : public base::RefCounted<ActiveCall> {
113  public:
ActiveCall(const base::WeakPtr<AppCacheDiskCache> & owner,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)114   ActiveCall(const base::WeakPtr<AppCacheDiskCache>& owner,
115              AppCacheDiskCacheEntry** entry,
116              net::CompletionOnceCallback callback)
117       : owner_(owner), entry_(entry), callback_(std::move(callback)) {
118     DCHECK(owner_);
119   }
120 
CreateEntry(const base::WeakPtr<AppCacheDiskCache> & owner,int64_t key,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)121   static net::Error CreateEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
122                                 int64_t key,
123                                 AppCacheDiskCacheEntry** entry,
124                                 net::CompletionOnceCallback callback) {
125     scoped_refptr<ActiveCall> active_call =
126         base::MakeRefCounted<ActiveCall>(owner, entry, std::move(callback));
127     disk_cache::EntryResult result = owner->disk_cache()->CreateEntry(
128         base::NumberToString(key), net::HIGHEST,
129         base::BindOnce(&ActiveCall::OnAsyncCompletion, active_call));
130     return active_call->HandleImmediateReturnValue(std::move(result));
131   }
132 
OpenEntry(const base::WeakPtr<AppCacheDiskCache> & owner,int64_t key,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)133   static net::Error OpenEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
134                               int64_t key,
135                               AppCacheDiskCacheEntry** entry,
136                               net::CompletionOnceCallback callback) {
137     scoped_refptr<ActiveCall> active_call =
138         base::MakeRefCounted<ActiveCall>(owner, entry, std::move(callback));
139     disk_cache::EntryResult result = owner->disk_cache()->OpenEntry(
140         base::NumberToString(key), net::HIGHEST,
141         base::BindOnce(&ActiveCall::OnAsyncCompletion, active_call));
142     return active_call->HandleImmediateReturnValue(std::move(result));
143   }
144 
DoomEntry(const base::WeakPtr<AppCacheDiskCache> & owner,int64_t key,net::CompletionOnceCallback callback)145   static net::Error DoomEntry(const base::WeakPtr<AppCacheDiskCache>& owner,
146                               int64_t key,
147                               net::CompletionOnceCallback callback) {
148     return owner->disk_cache()->DoomEntry(base::NumberToString(key),
149                                           net::HIGHEST, std::move(callback));
150   }
151 
152  private:
153   friend class base::RefCounted<ActiveCall>;
154 
155   ~ActiveCall() = default;
156 
HandleImmediateReturnValue(disk_cache::EntryResult result)157   net::Error HandleImmediateReturnValue(disk_cache::EntryResult result) {
158     net::Error rv = result.net_error();
159     if (rv == net::ERR_IO_PENDING) {
160       // OnAsyncCompletion will be called later.
161       return rv;
162     }
163 
164     if (rv == net::OK) {
165       *entry_ = new AppCacheDiskCacheEntry(result.ReleaseEntry(), owner_.get());
166     }
167 
168     return rv;
169   }
170 
OnAsyncCompletion(disk_cache::EntryResult result)171   void OnAsyncCompletion(disk_cache::EntryResult result) {
172     int rv = result.net_error();
173     if (rv == net::OK) {
174       if (owner_) {
175         *entry_ =
176             new AppCacheDiskCacheEntry(result.ReleaseEntry(), owner_.get());
177       } else {
178         result.ReleaseEntry()->Close();
179         rv = net::ERR_ABORTED;
180       }
181     }
182     std::move(callback_).Run(rv);
183   }
184 
185   base::WeakPtr<AppCacheDiskCache> owner_;
186   AppCacheDiskCacheEntry** entry_;
187   net::CompletionOnceCallback callback_;
188 };
189 
190 }  // namespace
191 
AppCacheDiskCache()192 AppCacheDiskCache::AppCacheDiskCache()
193 #if defined(APPCACHE_USE_SIMPLE_CACHE)
194     : AppCacheDiskCache(true)
195 #else
196     : AppCacheDiskCache(false)
197 #endif
198 {
199 }
200 
~AppCacheDiskCache()201 AppCacheDiskCache::~AppCacheDiskCache() {
202   Disable();
203 }
204 
InitWithDiskBackend(const base::FilePath & disk_cache_directory,bool force,base::OnceClosure post_cleanup_callback,net::CompletionOnceCallback callback)205 net::Error AppCacheDiskCache::InitWithDiskBackend(
206     const base::FilePath& disk_cache_directory,
207     bool force,
208     base::OnceClosure post_cleanup_callback,
209     net::CompletionOnceCallback callback) {
210   return Init(net::APP_CACHE, disk_cache_directory,
211               std::numeric_limits<int64_t>::max(), force,
212               std::move(post_cleanup_callback), std::move(callback));
213 }
214 
InitWithMemBackend(int64_t mem_cache_size,net::CompletionOnceCallback callback)215 net::Error AppCacheDiskCache::InitWithMemBackend(
216     int64_t mem_cache_size,
217     net::CompletionOnceCallback callback) {
218   return Init(net::MEMORY_CACHE, base::FilePath(), mem_cache_size, false,
219               base::OnceClosure(), std::move(callback));
220 }
221 
Disable()222 void AppCacheDiskCache::Disable() {
223   if (is_disabled_)
224     return;
225 
226   is_disabled_ = true;
227 
228   if (create_backend_callback_.get()) {
229     create_backend_callback_->Cancel();
230     create_backend_callback_ = nullptr;
231     OnCreateBackendComplete(net::ERR_ABORTED);
232   }
233 
234   // We need to close open file handles in order to reinitalize the
235   // appcache system on the fly. File handles held in both entries and in
236   // the main disk_cache::Backend class need to be released.
237   for (AppCacheDiskCacheEntry* entry : open_entries_) {
238     entry->Abandon();
239   }
240   open_entries_.clear();
241   disk_cache_.reset();
242 }
243 
CreateEntry(int64_t key,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)244 net::Error AppCacheDiskCache::CreateEntry(
245     int64_t key,
246     AppCacheDiskCacheEntry** entry,
247     net::CompletionOnceCallback callback) {
248   DCHECK(entry);
249   DCHECK(!callback.is_null());
250   if (is_disabled_)
251     return net::ERR_ABORTED;
252 
253   if (is_initializing_or_waiting_to_initialize()) {
254     pending_calls_.emplace_back(CREATE, key, entry, std::move(callback));
255     return net::ERR_IO_PENDING;
256   }
257 
258   if (!disk_cache_)
259     return net::ERR_FAILED;
260 
261   return ActiveCall::CreateEntry(weak_factory_.GetWeakPtr(), key, entry,
262                                  std::move(callback));
263 }
264 
OpenEntry(int64_t key,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)265 net::Error AppCacheDiskCache::OpenEntry(int64_t key,
266                                         AppCacheDiskCacheEntry** entry,
267                                         net::CompletionOnceCallback callback) {
268   DCHECK(entry);
269   DCHECK(!callback.is_null());
270   if (is_disabled_)
271     return net::ERR_ABORTED;
272 
273   if (is_initializing_or_waiting_to_initialize()) {
274     pending_calls_.emplace_back(OPEN, key, entry, std::move(callback));
275     return net::ERR_IO_PENDING;
276   }
277 
278   if (!disk_cache_)
279     return net::ERR_FAILED;
280 
281   return ActiveCall::OpenEntry(weak_factory_.GetWeakPtr(), key, entry,
282                                std::move(callback));
283 }
284 
DoomEntry(int64_t key,net::CompletionOnceCallback callback)285 net::Error AppCacheDiskCache::DoomEntry(int64_t key,
286                                         net::CompletionOnceCallback callback) {
287   DCHECK(!callback.is_null());
288   if (is_disabled_)
289     return net::ERR_ABORTED;
290 
291   if (is_initializing_or_waiting_to_initialize()) {
292     pending_calls_.emplace_back(DOOM, key, nullptr, std::move(callback));
293     return net::ERR_IO_PENDING;
294   }
295 
296   if (!disk_cache_)
297     return net::ERR_FAILED;
298 
299   return ActiveCall::DoomEntry(weak_factory_.GetWeakPtr(), key,
300                                std::move(callback));
301 }
302 
GetWeakPtr()303 base::WeakPtr<AppCacheDiskCache> AppCacheDiskCache::GetWeakPtr() {
304   return weak_factory_.GetWeakPtr();
305 }
306 
AppCacheDiskCache(bool use_simple_cache)307 AppCacheDiskCache::AppCacheDiskCache(bool use_simple_cache)
308     : use_simple_cache_(use_simple_cache),
309       is_disabled_(false),
310       is_waiting_to_initialize_(false) {}
311 
PendingCall(PendingCallType call_type,int64_t key,AppCacheDiskCacheEntry ** entry,net::CompletionOnceCallback callback)312 AppCacheDiskCache::PendingCall::PendingCall(
313     PendingCallType call_type,
314     int64_t key,
315     AppCacheDiskCacheEntry** entry,
316     net::CompletionOnceCallback callback)
317     : call_type(call_type),
318       key(key),
319       entry(entry),
320       callback(std::move(callback)) {}
321 
322 AppCacheDiskCache::PendingCall::PendingCall(PendingCall&&) = default;
323 
324 AppCacheDiskCache::PendingCall::~PendingCall() = default;
325 
Init(net::CacheType cache_type,const base::FilePath & cache_directory,int64_t cache_size,bool force,base::OnceClosure post_cleanup_callback,net::CompletionOnceCallback callback)326 net::Error AppCacheDiskCache::Init(net::CacheType cache_type,
327                                    const base::FilePath& cache_directory,
328                                    int64_t cache_size,
329                                    bool force,
330                                    base::OnceClosure post_cleanup_callback,
331                                    net::CompletionOnceCallback callback) {
332   DCHECK(!is_initializing_or_waiting_to_initialize() && !disk_cache_.get());
333   is_disabled_ = false;
334   create_backend_callback_ =
335       base::MakeRefCounted<CreateBackendCallbackShim>(this);
336   disk_cache::ResetHandling reset_handling =
337       force ? disk_cache::ResetHandling::kResetOnError
338             : disk_cache::ResetHandling::kNeverReset;
339 
340   net::Error return_value = disk_cache::CreateCacheBackend(
341       cache_type,
342       use_simple_cache_ ? net::CACHE_BACKEND_SIMPLE
343                         : net::CACHE_BACKEND_DEFAULT,
344       cache_directory, cache_size, reset_handling, nullptr,
345       &(create_backend_callback_->backend_ptr_),
346       std::move(post_cleanup_callback),
347       base::BindOnce(&CreateBackendCallbackShim::Callback,
348                      create_backend_callback_));
349   if (return_value == net::ERR_IO_PENDING)
350     init_callback_ = std::move(callback);
351   else
352     OnCreateBackendComplete(return_value);
353   return return_value;
354 }
355 
OnCreateBackendComplete(int return_value)356 void AppCacheDiskCache::OnCreateBackendComplete(int return_value) {
357   if (return_value == net::OK) {
358     disk_cache_ = std::move(create_backend_callback_->backend_ptr_);
359   }
360   create_backend_callback_ = nullptr;
361 
362   // Invoke our clients callback function.
363   if (!init_callback_.is_null()) {
364     std::move(init_callback_).Run(return_value);
365   }
366 
367   // Service pending calls that were queued up while we were initializing.
368   for (auto& call : pending_calls_) {
369     // This is safe, because the callback will only be called once.
370     net::CompletionRepeatingCallback copyable_callback =
371         base::AdaptCallbackForRepeating(std::move(call.callback));
372     return_value = net::ERR_FAILED;
373     switch (call.call_type) {
374       case CREATE:
375         return_value = CreateEntry(call.key, call.entry, copyable_callback);
376         break;
377       case OPEN:
378         return_value = OpenEntry(call.key, call.entry, copyable_callback);
379         break;
380       case DOOM:
381         return_value = DoomEntry(call.key, copyable_callback);
382         break;
383     }
384 
385     // disk_cache::{Create,Open,Doom}Entry() call their callbacks iff they
386     // return net::ERR_IO_PENDING. In this case, the callback was not called.
387     // However, the corresponding ServiceWorkerDiskCache wrapper returned
388     // net::ERR_IO_PENDING as it queued up the pending call. To follow the
389     // disk_cache API contract, we need to call the callback ourselves here.
390     if (return_value != net::ERR_IO_PENDING)
391       copyable_callback.Run(return_value);
392   }
393   pending_calls_.clear();
394 }
395 
396 }  // namespace content
397