Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/ArrayUtils.h"
8 : #include "mozilla/Attributes.h"
9 : #include "mozilla/Assertions.h"
10 : #include "mozilla/DebugOnly.h"
11 :
12 : #include "necko-config.h"
13 :
14 : #include "nsCache.h"
15 : #include "nsCacheService.h"
16 : #include "nsCacheRequest.h"
17 : #include "nsCacheEntry.h"
18 : #include "nsCacheEntryDescriptor.h"
19 : #include "nsCacheDevice.h"
20 : #include "nsMemoryCacheDevice.h"
21 : #include "nsICacheVisitor.h"
22 : #include "nsDiskCacheDevice.h"
23 : #include "nsDiskCacheDeviceSQL.h"
24 : #include "nsCacheUtils.h"
25 : #include "../cache2/CacheObserver.h"
26 :
27 : #include "nsIObserverService.h"
28 : #include "nsIPrefService.h"
29 : #include "nsIPrefBranch.h"
30 : #include "nsIFile.h"
31 : #include "nsIOService.h"
32 : #include "nsDirectoryServiceDefs.h"
33 : #include "nsAppDirectoryServiceDefs.h"
34 : #include "nsThreadUtils.h"
35 : #include "nsProxyRelease.h"
36 : #include "nsDeleteDir.h"
37 : #include "nsNetCID.h"
38 : #include <math.h> // for log()
39 : #include "mozilla/Services.h"
40 : #include "nsITimer.h"
41 : #include "mozIStorageService.h"
42 :
43 : #include "mozilla/net/NeckoCommon.h"
44 : #include <algorithm>
45 :
46 : using namespace mozilla;
47 : using namespace mozilla::net;
48 :
49 : /******************************************************************************
50 : * nsCacheProfilePrefObserver
51 : *****************************************************************************/
52 : #define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
53 : #define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
54 : #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
55 : "browser.cache.disk.smart_size.first_run"
56 : #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
57 : "browser.cache.disk.smart_size.enabled"
58 : #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
59 : #define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
60 : #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
61 : #define DISK_CACHE_CAPACITY 256000
62 :
63 : #define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
64 : "browser.cache.disk.smart_size.use_old_max"
65 :
66 : #define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
67 : #define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
68 : #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
69 : #define OFFLINE_CACHE_CAPACITY 512000
70 :
71 : #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
72 : #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
73 : #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
74 :
75 : #define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
76 : #define CACHE_COMPRESSION_LEVEL 1
77 :
78 : #define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
79 : #define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
80 :
81 : static const char * observerList[] = {
82 : "profile-before-change",
83 : "profile-do-change",
84 : NS_XPCOM_SHUTDOWN_OBSERVER_ID,
85 : "last-pb-context-exited",
86 : "suspend_process_notification",
87 : "resume_process_notification"
88 : };
89 :
90 : static const char * prefList[] = {
91 : DISK_CACHE_ENABLE_PREF,
92 : DISK_CACHE_SMART_SIZE_ENABLED_PREF,
93 : DISK_CACHE_CAPACITY_PREF,
94 : DISK_CACHE_DIR_PREF,
95 : DISK_CACHE_MAX_ENTRY_SIZE_PREF,
96 : DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
97 : OFFLINE_CACHE_ENABLE_PREF,
98 : OFFLINE_CACHE_CAPACITY_PREF,
99 : OFFLINE_CACHE_DIR_PREF,
100 : MEMORY_CACHE_ENABLE_PREF,
101 : MEMORY_CACHE_CAPACITY_PREF,
102 : MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
103 : CACHE_COMPRESSION_LEVEL_PREF,
104 : SANITIZE_ON_SHUTDOWN_PREF,
105 : CLEAR_ON_SHUTDOWN_PREF
106 : };
107 :
108 : // Cache sizes, in KB
109 : const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
110 : #ifdef ANDROID
111 : const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
112 : const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
113 : #else
114 : const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
115 : const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
116 : #endif
117 : // Default cache size was 50 MB for many years until FF 4:
118 : const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
119 :
120 : class nsCacheProfilePrefObserver : public nsIObserver
121 : {
122 0 : virtual ~nsCacheProfilePrefObserver() {}
123 :
124 : public:
125 : NS_DECL_THREADSAFE_ISUPPORTS
126 : NS_DECL_NSIOBSERVER
127 :
128 1 : nsCacheProfilePrefObserver()
129 1 : : mHaveProfile(false)
130 : , mDiskCacheEnabled(false)
131 : , mDiskCacheCapacity(0)
132 : , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
133 : , mSmartSizeEnabled(false)
134 : , mShouldUseOldMaxSmartSize(false)
135 : , mOfflineCacheEnabled(false)
136 : , mOfflineCacheCapacity(0)
137 : , mMemoryCacheEnabled(true)
138 : , mMemoryCacheCapacity(-1)
139 : , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
140 : , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
141 : , mSanitizeOnShutdown(false)
142 1 : , mClearCacheOnShutdown(false)
143 : {
144 1 : }
145 :
146 : nsresult Install();
147 : void Remove();
148 : nsresult ReadPrefs(nsIPrefBranch* branch);
149 :
150 : bool DiskCacheEnabled();
151 0 : int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
152 : void SetDiskCacheCapacity(int32_t);
153 0 : int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
154 1 : nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
155 0 : bool SmartSizeEnabled() { return mSmartSizeEnabled; }
156 :
157 0 : bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
158 0 : void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
159 :
160 : bool OfflineCacheEnabled();
161 0 : int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
162 0 : nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
163 :
164 : bool MemoryCacheEnabled();
165 : int32_t MemoryCacheCapacity();
166 0 : int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
167 :
168 : int32_t CacheCompressionLevel();
169 :
170 0 : bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
171 :
172 : static uint32_t GetSmartCacheSize(const nsAString& cachePath,
173 : uint32_t currentSize,
174 : bool shouldUseOldMaxSmartSize);
175 :
176 : bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
177 :
178 : private:
179 : bool mHaveProfile;
180 :
181 : bool mDiskCacheEnabled;
182 : int32_t mDiskCacheCapacity; // in kilobytes
183 : int32_t mDiskCacheMaxEntrySize; // in kilobytes
184 : nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
185 : bool mSmartSizeEnabled;
186 :
187 : bool mShouldUseOldMaxSmartSize;
188 :
189 : bool mOfflineCacheEnabled;
190 : int32_t mOfflineCacheCapacity; // in kilobytes
191 : nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
192 :
193 : bool mMemoryCacheEnabled;
194 : int32_t mMemoryCacheCapacity; // in kilobytes
195 : int32_t mMemoryCacheMaxEntrySize; // in kilobytes
196 :
197 : int32_t mCacheCompressionLevel;
198 :
199 : bool mSanitizeOnShutdown;
200 : bool mClearCacheOnShutdown;
201 : };
202 :
203 127 : NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
204 :
205 0 : class nsSetDiskSmartSizeCallback final : public nsITimerCallback
206 : {
207 0 : ~nsSetDiskSmartSizeCallback() {}
208 :
209 : public:
210 : NS_DECL_THREADSAFE_ISUPPORTS
211 :
212 0 : NS_IMETHOD Notify(nsITimer* aTimer) override {
213 0 : if (nsCacheService::gService) {
214 0 : nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
215 0 : nsCacheService::gService->SetDiskSmartSize_Locked();
216 0 : nsCacheService::gService->mSmartSizeTimer = nullptr;
217 : }
218 0 : return NS_OK;
219 : }
220 : };
221 :
222 0 : NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
223 :
224 : // Runnable sent to main thread after the cache IO thread calculates available
225 : // disk space, so that there is no race in setting mDiskCacheCapacity.
226 0 : class nsSetSmartSizeEvent: public Runnable
227 : {
228 : public:
229 0 : explicit nsSetSmartSizeEvent(int32_t smartSize)
230 0 : : mozilla::Runnable("nsSetSmartSizeEvent")
231 0 : , mSmartSize(smartSize)
232 : {
233 0 : }
234 :
235 0 : NS_IMETHOD Run()
236 : {
237 0 : NS_ASSERTION(NS_IsMainThread(),
238 : "Setting smart size data off the main thread");
239 :
240 : // Main thread may have already called nsCacheService::Shutdown
241 0 : if (!nsCacheService::IsInitialized())
242 0 : return NS_ERROR_NOT_AVAILABLE;
243 :
244 : // Ensure smart sizing wasn't switched off while event was pending.
245 : // It is safe to access the observer without the lock since we are
246 : // on the main thread and the value changes only on the main thread.
247 0 : if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
248 0 : return NS_OK;
249 :
250 0 : nsCacheService::SetDiskCacheCapacity(mSmartSize);
251 :
252 0 : nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
253 0 : if (!ps ||
254 0 : NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
255 0 : NS_WARNING("Failed to set smart size pref");
256 :
257 0 : return NS_OK;
258 : }
259 :
260 : private:
261 : int32_t mSmartSize;
262 : };
263 :
264 :
265 : // Runnable sent from main thread to cacheIO thread
266 0 : class nsGetSmartSizeEvent: public Runnable
267 : {
268 : public:
269 0 : nsGetSmartSizeEvent(const nsAString& cachePath,
270 : uint32_t currentSize,
271 : bool shouldUseOldMaxSmartSize)
272 0 : : mozilla::Runnable("nsGetSmartSizeEvent")
273 : , mCachePath(cachePath)
274 : , mCurrentSize(currentSize)
275 0 : , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
276 : {
277 0 : }
278 :
279 : // Calculates user's disk space available on a background thread and
280 : // dispatches this value back to the main thread.
281 0 : NS_IMETHOD Run() override
282 : {
283 : uint32_t size;
284 0 : size = nsCacheProfilePrefObserver::GetSmartCacheSize(
285 0 : mCachePath, mCurrentSize, mShouldUseOldMaxSmartSize);
286 0 : NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
287 0 : return NS_OK;
288 : }
289 :
290 : private:
291 : nsString mCachePath;
292 : uint32_t mCurrentSize;
293 : bool mShouldUseOldMaxSmartSize;
294 : };
295 :
296 0 : class nsBlockOnCacheThreadEvent : public Runnable {
297 : public:
298 0 : nsBlockOnCacheThreadEvent()
299 0 : : mozilla::Runnable("nsBlockOnCacheThreadEvent")
300 : {
301 0 : }
302 0 : NS_IMETHOD Run() override
303 : {
304 0 : nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
305 0 : CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
306 0 : nsCacheService::gService->mNotified = true;
307 0 : nsCacheService::gService->mCondVar.Notify();
308 0 : return NS_OK;
309 : }
310 : };
311 :
312 :
313 : nsresult
314 1 : nsCacheProfilePrefObserver::Install()
315 : {
316 : // install profile-change observer
317 : nsCOMPtr<nsIObserverService> observerService =
318 2 : mozilla::services::GetObserverService();
319 1 : if (!observerService)
320 0 : return NS_ERROR_FAILURE;
321 :
322 1 : nsresult rv, rv2 = NS_OK;
323 7 : for (unsigned int i=0; i<ArrayLength(observerList); i++) {
324 6 : rv = observerService->AddObserver(this, observerList[i], false);
325 6 : if (NS_FAILED(rv))
326 0 : rv2 = rv;
327 : }
328 :
329 : // install preferences observer
330 2 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
331 1 : if (!branch) return NS_ERROR_FAILURE;
332 :
333 16 : for (unsigned int i=0; i<ArrayLength(prefList); i++) {
334 15 : rv = branch->AddObserver(prefList[i], this, false);
335 15 : if (NS_FAILED(rv))
336 0 : rv2 = rv;
337 : }
338 :
339 : // Determine if we have a profile already
340 : // Install() is called *after* the profile-after-change notification
341 : // when there is only a single profile, or it is specified on the
342 : // commandline at startup.
343 : // In that case, we detect the presence of a profile by the existence
344 : // of the NS_APP_USER_PROFILE_50_DIR directory.
345 :
346 2 : nsCOMPtr<nsIFile> directory;
347 1 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
348 2 : getter_AddRefs(directory));
349 1 : if (NS_SUCCEEDED(rv))
350 1 : mHaveProfile = true;
351 :
352 1 : rv = ReadPrefs(branch);
353 1 : NS_ENSURE_SUCCESS(rv, rv);
354 :
355 1 : return rv2;
356 : }
357 :
358 :
359 : void
360 0 : nsCacheProfilePrefObserver::Remove()
361 : {
362 : // remove Observer Service observers
363 : nsCOMPtr<nsIObserverService> obs =
364 0 : mozilla::services::GetObserverService();
365 0 : if (obs) {
366 0 : for (unsigned int i=0; i<ArrayLength(observerList); i++) {
367 0 : obs->RemoveObserver(this, observerList[i]);
368 : }
369 : }
370 :
371 : // remove Pref Service observers
372 : nsCOMPtr<nsIPrefBranch> prefs =
373 0 : do_GetService(NS_PREFSERVICE_CONTRACTID);
374 0 : if (!prefs)
375 0 : return;
376 0 : for (unsigned int i=0; i<ArrayLength(prefList); i++)
377 0 : prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
378 : }
379 :
380 : void
381 0 : nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
382 : {
383 0 : mDiskCacheCapacity = std::max(0, capacity);
384 0 : }
385 :
386 :
387 : NS_IMETHODIMP
388 0 : nsCacheProfilePrefObserver::Observe(nsISupports * subject,
389 : const char * topic,
390 : const char16_t * data_unicode)
391 : {
392 : nsresult rv;
393 0 : NS_ConvertUTF16toUTF8 data(data_unicode);
394 0 : CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
395 :
396 0 : if (!nsCacheService::IsInitialized()) {
397 0 : if (!strcmp("resume_process_notification", topic)) {
398 : // A suspended process has a closed cache, so re-open it here.
399 0 : nsCacheService::GlobalInstance()->Init();
400 : }
401 0 : return NS_OK;
402 : }
403 :
404 0 : if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
405 : // xpcom going away, shutdown cache service
406 0 : nsCacheService::GlobalInstance()->Shutdown();
407 0 : } else if (!strcmp("profile-before-change", topic)) {
408 : // profile before change
409 0 : mHaveProfile = false;
410 :
411 : // XXX shutdown devices
412 0 : nsCacheService::OnProfileShutdown();
413 0 : } else if (!strcmp("suspend_process_notification", topic)) {
414 : // A suspended process may never return, so shutdown the cache to reduce
415 : // cache corruption.
416 0 : nsCacheService::GlobalInstance()->Shutdown();
417 0 : } else if (!strcmp("profile-do-change", topic)) {
418 : // profile after change
419 0 : mHaveProfile = true;
420 0 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
421 0 : if (!branch) {
422 0 : return NS_ERROR_FAILURE;
423 : }
424 0 : (void)ReadPrefs(branch);
425 0 : nsCacheService::OnProfileChanged();
426 :
427 0 : } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
428 :
429 : // ignore pref changes until we're done switch profiles
430 0 : if (!mHaveProfile)
431 0 : return NS_OK;
432 :
433 0 : nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
434 0 : if (NS_FAILED(rv))
435 0 : return rv;
436 :
437 : // which preference changed?
438 0 : if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
439 :
440 0 : rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
441 0 : &mDiskCacheEnabled);
442 0 : if (NS_FAILED(rv))
443 0 : return rv;
444 0 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
445 :
446 0 : } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
447 :
448 0 : int32_t capacity = 0;
449 0 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
450 0 : if (NS_FAILED(rv))
451 0 : return rv;
452 0 : mDiskCacheCapacity = std::max(0, capacity);
453 0 : nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
454 :
455 : // Update the cache capacity when smart sizing is turned on/off
456 0 : } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
457 : // Is the update because smartsizing was turned on, or off?
458 0 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
459 0 : &mSmartSizeEnabled);
460 0 : if (NS_FAILED(rv))
461 0 : return rv;
462 0 : int32_t newCapacity = 0;
463 0 : if (mSmartSizeEnabled) {
464 0 : nsCacheService::SetDiskSmartSize();
465 : } else {
466 : // Smart sizing switched off: use user specified size
467 0 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
468 0 : if (NS_FAILED(rv))
469 0 : return rv;
470 0 : mDiskCacheCapacity = std::max(0, newCapacity);
471 0 : nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
472 : }
473 0 : } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
474 0 : rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
475 0 : &mShouldUseOldMaxSmartSize);
476 0 : if (NS_FAILED(rv))
477 0 : return rv;
478 0 : } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
479 : int32_t newMaxSize;
480 0 : rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
481 0 : &newMaxSize);
482 0 : if (NS_FAILED(rv))
483 0 : return rv;
484 :
485 0 : mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
486 0 : nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
487 :
488 : #if 0
489 : } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
490 : // XXX We probaby don't want to respond to this pref except after
491 : // XXX profile changes. Ideally, there should be somekind of user
492 : // XXX notification that the pref change won't take effect until
493 : // XXX the next time the profile changes (browser launch)
494 : #endif
495 : } else
496 :
497 : // which preference changed?
498 0 : if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
499 :
500 0 : rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
501 0 : &mOfflineCacheEnabled);
502 0 : if (NS_FAILED(rv)) return rv;
503 0 : nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
504 :
505 0 : } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
506 :
507 0 : int32_t capacity = 0;
508 0 : rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
509 0 : if (NS_FAILED(rv)) return rv;
510 0 : mOfflineCacheCapacity = std::max(0, capacity);
511 0 : nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
512 : #if 0
513 : } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
514 : // XXX We probaby don't want to respond to this pref except after
515 : // XXX profile changes. Ideally, there should be some kind of user
516 : // XXX notification that the pref change won't take effect until
517 : // XXX the next time the profile changes (browser launch)
518 : #endif
519 : } else
520 :
521 0 : if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
522 :
523 0 : rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
524 0 : &mMemoryCacheEnabled);
525 0 : if (NS_FAILED(rv))
526 0 : return rv;
527 0 : nsCacheService::SetMemoryCache();
528 :
529 0 : } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
530 :
531 0 : mMemoryCacheCapacity = -1;
532 0 : (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
533 0 : &mMemoryCacheCapacity);
534 0 : nsCacheService::SetMemoryCache();
535 0 : } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
536 : int32_t newMaxSize;
537 0 : rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
538 0 : &newMaxSize);
539 0 : if (NS_FAILED(rv))
540 0 : return rv;
541 :
542 0 : mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
543 0 : nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
544 0 : } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
545 0 : mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
546 0 : (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
547 0 : &mCacheCompressionLevel);
548 0 : mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
549 0 : mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
550 0 : } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
551 0 : rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
552 0 : &mSanitizeOnShutdown);
553 0 : if (NS_FAILED(rv))
554 0 : return rv;
555 0 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
556 0 : } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
557 0 : rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
558 0 : &mClearCacheOnShutdown);
559 0 : if (NS_FAILED(rv))
560 0 : return rv;
561 0 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
562 : }
563 0 : } else if (!strcmp("last-pb-context-exited", topic)) {
564 0 : nsCacheService::LeavePrivateBrowsing();
565 : }
566 :
567 0 : return NS_OK;
568 : }
569 :
570 : // Returns default ("smart") size (in KB) of cache, given available disk space
571 : // (also in KB)
572 : static uint32_t
573 0 : SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
574 : {
575 0 : uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
576 :
577 0 : if (availKB > 100 * 1024 * 1024)
578 0 : return maxSize; // skip computing if we're over 100 GB
579 :
580 : // Grow/shrink in 10 MB units, deliberately, so that in the common case we
581 : // don't shrink cache and evict items every time we startup (it's important
582 : // that we don't slow down startup benchmarks).
583 0 : uint32_t sz10MBs = 0;
584 0 : uint32_t avail10MBs = availKB / (1024*10);
585 :
586 : // .5% of space above 25 GB
587 0 : if (avail10MBs > 2500) {
588 0 : sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
589 0 : avail10MBs = 2500;
590 : }
591 : // 1% of space between 7GB -> 25 GB
592 0 : if (avail10MBs > 700) {
593 0 : sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
594 0 : avail10MBs = 700;
595 : }
596 : // 5% of space between 500 MB -> 7 GB
597 0 : if (avail10MBs > 50) {
598 0 : sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
599 0 : avail10MBs = 50;
600 : }
601 :
602 : #ifdef ANDROID
603 : // On Android, smaller/older devices may have very little storage and
604 : // device owners may be sensitive to storage footprint: Use a smaller
605 : // percentage of available space and a smaller minimum.
606 :
607 : // 20% of space up to 500 MB (10 MB min)
608 : sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
609 : #else
610 : // 40% of space up to 500 MB (50 MB min)
611 0 : sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
612 : #endif
613 :
614 0 : return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
615 : }
616 :
617 : /* Computes our best guess for the default size of the user's disk cache,
618 : * based on the amount of space they have free on their hard drive.
619 : * We use a tiered scheme: the more space available,
620 : * the larger the disk cache will be. However, we do not want
621 : * to enable the disk cache to grow to an unbounded size, so the larger the
622 : * user's available space is, the smaller of a percentage we take. We set a
623 : * lower bound of 50MB and an upper bound of 1GB.
624 : *
625 : *@param: None.
626 : *@return: The size that the user's disk cache should default to, in kBytes.
627 : */
628 : uint32_t
629 0 : nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
630 : uint32_t currentSize,
631 : bool shouldUseOldMaxSmartSize)
632 : {
633 : // Check for free space on device where cache directory lives
634 : nsresult rv;
635 : nsCOMPtr<nsIFile>
636 0 : cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
637 0 : if (NS_FAILED(rv) || !cacheDirectory)
638 0 : return DEFAULT_CACHE_SIZE;
639 0 : rv = cacheDirectory->InitWithPath(cachePath);
640 0 : if (NS_FAILED(rv))
641 0 : return DEFAULT_CACHE_SIZE;
642 : int64_t bytesAvailable;
643 0 : rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
644 0 : if (NS_FAILED(rv))
645 0 : return DEFAULT_CACHE_SIZE;
646 :
647 0 : return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
648 : currentSize),
649 0 : shouldUseOldMaxSmartSize);
650 : }
651 :
652 : /* Determine if we are permitted to dynamically size the user's disk cache based
653 : * on their disk space available. We may do this so long as the pref
654 : * smart_size.enabled is true.
655 : */
656 : bool
657 1 : nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
658 : firstRun)
659 : {
660 : nsresult rv;
661 1 : if (firstRun) {
662 : // check if user has set cache size in the past
663 : bool userSet;
664 0 : rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
665 0 : if (NS_FAILED(rv)) userSet = true;
666 0 : if (userSet) {
667 : int32_t oldCapacity;
668 : // If user explicitly set cache size to be smaller than old default
669 : // of 50 MB, then keep user's value. Otherwise use smart sizing.
670 0 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
671 0 : if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
672 0 : mSmartSizeEnabled = false;
673 0 : branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
674 0 : mSmartSizeEnabled);
675 0 : return mSmartSizeEnabled;
676 : }
677 : }
678 : // Set manual setting to MAX cache size as starting val for any
679 : // adjustment by user: (bug 559942 comment 65)
680 0 : int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
681 0 : branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
682 : }
683 :
684 1 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
685 2 : &mSmartSizeEnabled);
686 1 : if (NS_FAILED(rv))
687 0 : mSmartSizeEnabled = false;
688 1 : return mSmartSizeEnabled;
689 : }
690 :
691 :
692 : nsresult
693 1 : nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
694 : {
695 1 : nsresult rv = NS_OK;
696 :
697 : // read disk cache device prefs
698 1 : mDiskCacheEnabled = true; // presume disk cache is enabled
699 1 : (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
700 :
701 1 : mDiskCacheCapacity = DISK_CACHE_CAPACITY;
702 1 : (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
703 1 : mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
704 :
705 1 : (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
706 2 : &mDiskCacheMaxEntrySize);
707 1 : mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
708 :
709 1 : (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
710 : NS_GET_IID(nsIFile),
711 2 : getter_AddRefs(mDiskCacheParentDirectory));
712 :
713 1 : (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
714 2 : &mShouldUseOldMaxSmartSize);
715 :
716 1 : if (!mDiskCacheParentDirectory) {
717 2 : nsCOMPtr<nsIFile> directory;
718 :
719 : // try to get the disk cache parent directory
720 1 : rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
721 2 : getter_AddRefs(directory));
722 1 : if (NS_FAILED(rv)) {
723 : // try to get the profile directory (there may not be a profile yet)
724 2 : nsCOMPtr<nsIFile> profDir;
725 1 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
726 2 : getter_AddRefs(profDir));
727 1 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
728 2 : getter_AddRefs(directory));
729 1 : if (!directory)
730 0 : directory = profDir;
731 1 : else if (profDir) {
732 1 : nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
733 1 : "Cache");
734 : }
735 : }
736 : // use file cache in build tree only if asked, to avoid cache dir litter
737 1 : if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
738 0 : rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
739 0 : getter_AddRefs(directory));
740 : }
741 1 : if (directory)
742 1 : mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
743 : }
744 1 : if (mDiskCacheParentDirectory) {
745 : bool firstSmartSizeRun;
746 1 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
747 1 : &firstSmartSizeRun);
748 1 : if (NS_FAILED(rv))
749 0 : firstSmartSizeRun = false;
750 1 : if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
751 : // Avoid evictions: use previous cache size until smart size event
752 : // updates mDiskCacheCapacity
753 0 : rv = branch->GetIntPref(firstSmartSizeRun ?
754 : DISK_CACHE_CAPACITY_PREF :
755 : DISK_CACHE_SMART_SIZE_PREF,
756 0 : &mDiskCacheCapacity);
757 0 : if (NS_FAILED(rv))
758 0 : mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
759 : }
760 :
761 1 : if (firstSmartSizeRun) {
762 : // It is no longer our first run
763 0 : rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
764 0 : false);
765 0 : if (NS_FAILED(rv))
766 0 : NS_WARNING("Failed setting first_run pref in ReadPrefs.");
767 : }
768 : }
769 :
770 : // read offline cache device prefs
771 1 : mOfflineCacheEnabled = true; // presume offline cache is enabled
772 1 : (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
773 2 : &mOfflineCacheEnabled);
774 :
775 1 : mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
776 1 : (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
777 2 : &mOfflineCacheCapacity);
778 1 : mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
779 :
780 1 : (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
781 : NS_GET_IID(nsIFile),
782 2 : getter_AddRefs(mOfflineCacheParentDirectory));
783 :
784 1 : if (!mOfflineCacheParentDirectory) {
785 2 : nsCOMPtr<nsIFile> directory;
786 :
787 : // try to get the offline cache parent directory
788 1 : rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
789 2 : getter_AddRefs(directory));
790 1 : if (NS_FAILED(rv)) {
791 : // try to get the profile directory (there may not be a profile yet)
792 2 : nsCOMPtr<nsIFile> profDir;
793 1 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
794 2 : getter_AddRefs(profDir));
795 1 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
796 2 : getter_AddRefs(directory));
797 1 : if (!directory)
798 0 : directory = profDir;
799 1 : else if (profDir) {
800 1 : nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
801 1 : "OfflineCache");
802 : }
803 : }
804 : #if DEBUG
805 1 : if (!directory) {
806 : // use current process directory during development
807 0 : rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
808 0 : getter_AddRefs(directory));
809 : }
810 : #endif
811 1 : if (directory)
812 1 : mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
813 : }
814 :
815 : // read memory cache device prefs
816 1 : (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
817 :
818 1 : mMemoryCacheCapacity = -1;
819 1 : (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
820 2 : &mMemoryCacheCapacity);
821 :
822 1 : (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
823 2 : &mMemoryCacheMaxEntrySize);
824 1 : mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
825 :
826 : // read cache compression level pref
827 1 : mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
828 1 : (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
829 2 : &mCacheCompressionLevel);
830 1 : mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
831 1 : mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
832 :
833 : // read cache shutdown sanitization prefs
834 1 : (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
835 2 : &mSanitizeOnShutdown);
836 1 : (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
837 2 : &mClearCacheOnShutdown);
838 :
839 1 : return rv;
840 : }
841 :
842 : nsresult
843 0 : nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
844 : {
845 0 : if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
846 0 : return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
847 : }
848 :
849 : nsresult
850 0 : nsCacheService::SyncWithCacheIOThread()
851 : {
852 0 : if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
853 0 : gService->mLock.AssertCurrentThreadOwns();
854 :
855 0 : nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
856 :
857 : // dispatch event - it will notify the monitor when it's done
858 : nsresult rv =
859 0 : gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
860 0 : if (NS_FAILED(rv)) {
861 0 : NS_WARNING("Failed dispatching block-event");
862 0 : return NS_ERROR_UNEXPECTED;
863 : }
864 :
865 : // wait until notified, then return
866 0 : gService->mNotified = false;
867 0 : while (!gService->mNotified) {
868 0 : gService->mCondVar.Wait();
869 : }
870 :
871 0 : return NS_OK;
872 : }
873 :
874 :
875 : bool
876 1 : nsCacheProfilePrefObserver::DiskCacheEnabled()
877 : {
878 1 : if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
879 1 : return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
880 : }
881 :
882 :
883 : bool
884 1 : nsCacheProfilePrefObserver::OfflineCacheEnabled()
885 : {
886 1 : if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
887 0 : return false;
888 :
889 1 : return mOfflineCacheEnabled;
890 : }
891 :
892 :
893 : bool
894 1 : nsCacheProfilePrefObserver::MemoryCacheEnabled()
895 : {
896 1 : if (mMemoryCacheCapacity == 0) return false;
897 1 : return mMemoryCacheEnabled;
898 : }
899 :
900 :
901 : /**
902 : * MemoryCacheCapacity
903 : *
904 : * If the browser.cache.memory.capacity preference is positive, we use that
905 : * value for the amount of memory available for the cache.
906 : *
907 : * If browser.cache.memory.capacity is zero, the memory cache is disabled.
908 : *
909 : * If browser.cache.memory.capacity is negative or not present, we use a
910 : * formula that grows less than linearly with the amount of system memory,
911 : * with an upper limit on the cache size. No matter how much physical RAM is
912 : * present, the default cache size would not exceed 32 MB. This maximum would
913 : * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
914 : *
915 : * RAM Cache
916 : * --- -----
917 : * 32 Mb 2 Mb
918 : * 64 Mb 4 Mb
919 : * 128 Mb 6 Mb
920 : * 256 Mb 10 Mb
921 : * 512 Mb 14 Mb
922 : * 1024 Mb 18 Mb
923 : * 2048 Mb 24 Mb
924 : * 4096 Mb 30 Mb
925 : *
926 : * The equation for this is (for cache size C and memory size K (kbytes)):
927 : * x = log2(K) - 14
928 : * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
929 : * if (C > 32) C = 32
930 : */
931 :
932 : int32_t
933 0 : nsCacheProfilePrefObserver::MemoryCacheCapacity()
934 : {
935 0 : int32_t capacity = mMemoryCacheCapacity;
936 0 : if (capacity >= 0) {
937 0 : CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
938 0 : return capacity;
939 : }
940 :
941 0 : static uint64_t bytes = PR_GetPhysicalMemorySize();
942 0 : CACHE_LOG_DEBUG(("Physical Memory size is %" PRIu64 "\n", bytes));
943 :
944 : // If getting the physical memory failed, arbitrarily assume
945 : // 32 MB of RAM. We use a low default to have a reasonable
946 : // size on all the devices we support.
947 0 : if (bytes == 0)
948 0 : bytes = 32 * 1024 * 1024;
949 :
950 : // Conversion from unsigned int64_t to double doesn't work on all platforms.
951 : // We need to truncate the value at INT64_MAX to make sure we don't
952 : // overflow.
953 0 : if (bytes > INT64_MAX)
954 0 : bytes = INT64_MAX;
955 :
956 0 : uint64_t kbytes = bytes >> 10;
957 :
958 0 : double kBytesD = double(kbytes);
959 :
960 0 : double x = log(kBytesD)/log(2.0) - 14;
961 0 : if (x > 0) {
962 0 : capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
963 0 : if (capacity > 32)
964 0 : capacity = 32;
965 0 : capacity *= 1024;
966 : } else {
967 0 : capacity = 0;
968 : }
969 :
970 0 : return capacity;
971 : }
972 :
973 : int32_t
974 0 : nsCacheProfilePrefObserver::CacheCompressionLevel()
975 : {
976 0 : return mCacheCompressionLevel;
977 : }
978 :
979 : /******************************************************************************
980 : * nsProcessRequestEvent
981 : *****************************************************************************/
982 :
983 : class nsProcessRequestEvent : public Runnable {
984 : public:
985 0 : explicit nsProcessRequestEvent(nsCacheRequest* aRequest)
986 0 : : mozilla::Runnable("nsProcessRequestEvent")
987 : {
988 0 : mRequest = aRequest;
989 0 : }
990 :
991 0 : NS_IMETHOD Run() override
992 : {
993 : nsresult rv;
994 :
995 0 : NS_ASSERTION(mRequest->mListener,
996 : "Sync OpenCacheEntry() posted to background thread!");
997 :
998 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
999 0 : rv = nsCacheService::gService->ProcessRequest(mRequest,
1000 : false,
1001 0 : nullptr);
1002 :
1003 : // Don't delete the request if it was queued
1004 0 : if (!(mRequest->IsBlocking() &&
1005 : rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
1006 0 : delete mRequest;
1007 :
1008 0 : return NS_OK;
1009 : }
1010 :
1011 : protected:
1012 0 : virtual ~nsProcessRequestEvent() {}
1013 :
1014 : private:
1015 : nsCacheRequest *mRequest;
1016 : };
1017 :
1018 : /******************************************************************************
1019 : * nsDoomEvent
1020 : *****************************************************************************/
1021 :
1022 0 : class nsDoomEvent : public Runnable {
1023 : public:
1024 0 : nsDoomEvent(nsCacheSession *session,
1025 : const nsACString &key,
1026 : nsICacheListener *listener)
1027 0 : : mozilla::Runnable("nsDoomEvent")
1028 : {
1029 0 : mKey = *session->ClientID();
1030 0 : mKey.Append(':');
1031 0 : mKey.Append(key);
1032 0 : mStoragePolicy = session->StoragePolicy();
1033 0 : mListener = listener;
1034 0 : mEventTarget = GetCurrentThreadEventTarget();
1035 : // We addref the listener here and release it in nsNotifyDoomListener
1036 : // on the callers thread. If posting of nsNotifyDoomListener event fails
1037 : // we leak the listener which is better than releasing it on a wrong
1038 : // thread.
1039 0 : NS_IF_ADDREF(mListener);
1040 0 : }
1041 :
1042 0 : NS_IMETHOD Run() override
1043 : {
1044 0 : nsCacheServiceAutoLock lock;
1045 :
1046 0 : bool foundActive = true;
1047 0 : nsresult status = NS_ERROR_NOT_AVAILABLE;
1048 : nsCacheEntry *entry;
1049 0 : entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
1050 0 : if (!entry) {
1051 0 : bool collision = false;
1052 0 : foundActive = false;
1053 0 : entry = nsCacheService::gService->SearchCacheDevices(&mKey,
1054 : mStoragePolicy,
1055 0 : &collision);
1056 : }
1057 :
1058 0 : if (entry) {
1059 0 : status = NS_OK;
1060 0 : nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
1061 : }
1062 :
1063 0 : if (mListener) {
1064 0 : mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
1065 0 : NS_DISPATCH_NORMAL);
1066 : // posted event will release the reference on the correct thread
1067 0 : mListener = nullptr;
1068 : }
1069 :
1070 0 : return NS_OK;
1071 : }
1072 :
1073 : private:
1074 : nsCString mKey;
1075 : nsCacheStoragePolicy mStoragePolicy;
1076 : nsICacheListener *mListener;
1077 : nsCOMPtr<nsIEventTarget> mEventTarget;
1078 : };
1079 :
1080 : /******************************************************************************
1081 : * nsCacheService
1082 : *****************************************************************************/
1083 : nsCacheService * nsCacheService::gService = nullptr;
1084 :
1085 13 : NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
1086 : nsIMemoryReporter)
1087 :
1088 1 : nsCacheService::nsCacheService()
1089 : : mObserver(nullptr),
1090 : mLock("nsCacheService.mLock"),
1091 : mCondVar(mLock, "nsCacheService.mCondVar"),
1092 : mNotified(false),
1093 : mTimeStampLock("nsCacheService.mTimeStampLock"),
1094 : mInitialized(false),
1095 : mClearingEntries(false),
1096 : mEnableMemoryDevice(true),
1097 : mEnableDiskDevice(true),
1098 : mMemoryDevice(nullptr),
1099 : mDiskDevice(nullptr),
1100 : mOfflineDevice(nullptr),
1101 : mTotalEntries(0),
1102 : mCacheHits(0),
1103 : mCacheMisses(0),
1104 : mMaxKeyLength(0),
1105 : mMaxDataSize(0),
1106 : mMaxMetaSize(0),
1107 : mDeactivateFailures(0),
1108 1 : mDeactivatedUnboundEntries(0)
1109 : {
1110 1 : NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
1111 1 : gService = this;
1112 :
1113 : // create list of cache devices
1114 1 : PR_INIT_CLIST(&mDoomedEntries);
1115 1 : }
1116 :
1117 0 : nsCacheService::~nsCacheService()
1118 : {
1119 0 : if (mInitialized) // Shutdown hasn't been called yet.
1120 0 : (void) Shutdown();
1121 :
1122 0 : if (mObserver) {
1123 0 : mObserver->Remove();
1124 0 : NS_RELEASE(mObserver);
1125 : }
1126 :
1127 0 : gService = nullptr;
1128 0 : }
1129 :
1130 :
1131 : nsresult
1132 1 : nsCacheService::Init()
1133 : {
1134 : // Thie method must be called on the main thread because mCacheIOThread must
1135 : // only be modified on the main thread.
1136 1 : if (!NS_IsMainThread()) {
1137 0 : NS_ERROR("nsCacheService::Init called off the main thread");
1138 0 : return NS_ERROR_NOT_SAME_THREAD;
1139 : }
1140 :
1141 1 : NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
1142 1 : if (mInitialized)
1143 0 : return NS_ERROR_ALREADY_INITIALIZED;
1144 :
1145 1 : if (mozilla::net::IsNeckoChild()) {
1146 0 : return NS_ERROR_UNEXPECTED;
1147 : }
1148 :
1149 : nsresult rv;
1150 :
1151 1 : mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
1152 1 : NS_ENSURE_SUCCESS(rv, rv);
1153 :
1154 1 : rv = NS_NewNamedThread("Cache I/O",
1155 2 : getter_AddRefs(mCacheIOThread));
1156 1 : if (NS_FAILED(rv)) {
1157 0 : MOZ_CRASH("Can't create cache IO thread");
1158 : }
1159 :
1160 1 : rv = nsDeleteDir::Init();
1161 1 : if (NS_FAILED(rv)) {
1162 0 : NS_WARNING("Can't initialize nsDeleteDir");
1163 : }
1164 :
1165 : // initialize hashtable for active cache entries
1166 1 : mActiveEntries.Init();
1167 :
1168 : // create profile/preference observer
1169 1 : if (!mObserver) {
1170 1 : mObserver = new nsCacheProfilePrefObserver();
1171 1 : NS_ADDREF(mObserver);
1172 1 : mObserver->Install();
1173 : }
1174 :
1175 1 : mEnableDiskDevice = mObserver->DiskCacheEnabled();
1176 1 : mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
1177 1 : mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
1178 :
1179 1 : RegisterWeakMemoryReporter(this);
1180 :
1181 1 : mInitialized = true;
1182 1 : return NS_OK;
1183 : }
1184 :
1185 : void
1186 0 : nsCacheService::Shutdown()
1187 : {
1188 : // This method must be called on the main thread because mCacheIOThread must
1189 : // only be modified on the main thread.
1190 0 : if (!NS_IsMainThread()) {
1191 0 : MOZ_CRASH("nsCacheService::Shutdown called off the main thread");
1192 : }
1193 :
1194 0 : nsCOMPtr<nsIThread> cacheIOThread;
1195 0 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
1196 :
1197 0 : bool shouldSanitize = false;
1198 0 : nsCOMPtr<nsIFile> parentDir;
1199 :
1200 : {
1201 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1202 0 : NS_ASSERTION(mInitialized,
1203 : "can't shutdown nsCacheService unless it has been initialized.");
1204 0 : if (!mInitialized)
1205 0 : return;
1206 :
1207 0 : mClearingEntries = true;
1208 0 : DoomActiveEntries(nullptr);
1209 : }
1210 :
1211 0 : CloseAllStreams();
1212 :
1213 0 : UnregisterWeakMemoryReporter(this);
1214 :
1215 : {
1216 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1217 0 : NS_ASSERTION(mInitialized, "Bad state");
1218 :
1219 0 : mInitialized = false;
1220 :
1221 : // Clear entries
1222 0 : ClearDoomList();
1223 :
1224 0 : if (mSmartSizeTimer) {
1225 0 : mSmartSizeTimer->Cancel();
1226 0 : mSmartSizeTimer = nullptr;
1227 : }
1228 :
1229 : // Make sure to wait for any pending cache-operations before
1230 : // proceeding with destructive actions (bug #620660)
1231 0 : (void) SyncWithCacheIOThread();
1232 0 : mActiveEntries.Shutdown();
1233 :
1234 : // obtain the disk cache directory in case we need to sanitize it
1235 0 : parentDir = mObserver->DiskCacheParentDirectory();
1236 0 : shouldSanitize = mObserver->SanitizeAtShutdown();
1237 :
1238 : // deallocate memory and disk caches
1239 0 : delete mMemoryDevice;
1240 0 : mMemoryDevice = nullptr;
1241 :
1242 0 : delete mDiskDevice;
1243 0 : mDiskDevice = nullptr;
1244 :
1245 0 : if (mOfflineDevice)
1246 0 : mOfflineDevice->Shutdown();
1247 :
1248 0 : NS_IF_RELEASE(mOfflineDevice);
1249 :
1250 0 : for (auto iter = mCustomOfflineDevices.Iter();
1251 0 : !iter.Done(); iter.Next()) {
1252 0 : iter.Data()->Shutdown();
1253 0 : iter.Remove();
1254 : }
1255 :
1256 0 : LogCacheStatistics();
1257 :
1258 0 : mClearingEntries = false;
1259 0 : mCacheIOThread.swap(cacheIOThread);
1260 : }
1261 :
1262 0 : if (cacheIOThread)
1263 0 : nsShutdownThread::BlockingShutdown(cacheIOThread);
1264 :
1265 0 : if (shouldSanitize) {
1266 0 : nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
1267 0 : if (NS_SUCCEEDED(rv)) {
1268 : bool exists;
1269 0 : if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
1270 0 : nsDeleteDir::DeleteDir(parentDir, false);
1271 : }
1272 0 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
1273 0 : nsDeleteDir::Shutdown(shouldSanitize);
1274 : } else {
1275 0 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
1276 0 : nsDeleteDir::Shutdown(shouldSanitize);
1277 : }
1278 : }
1279 :
1280 :
1281 : nsresult
1282 1 : nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
1283 : {
1284 : nsresult rv;
1285 :
1286 1 : if (aOuter != nullptr)
1287 0 : return NS_ERROR_NO_AGGREGATION;
1288 :
1289 1 : nsCacheService * cacheService = new nsCacheService();
1290 1 : if (cacheService == nullptr)
1291 0 : return NS_ERROR_OUT_OF_MEMORY;
1292 :
1293 1 : NS_ADDREF(cacheService);
1294 1 : rv = cacheService->Init();
1295 1 : if (NS_SUCCEEDED(rv)) {
1296 1 : rv = cacheService->QueryInterface(aIID, aResult);
1297 : }
1298 1 : NS_RELEASE(cacheService);
1299 1 : return rv;
1300 : }
1301 :
1302 :
1303 : NS_IMETHODIMP
1304 0 : nsCacheService::CreateSession(const char * clientID,
1305 : nsCacheStoragePolicy storagePolicy,
1306 : bool streamBased,
1307 : nsICacheSession **result)
1308 : {
1309 0 : *result = nullptr;
1310 :
1311 0 : if (net::CacheObserver::UseNewCache())
1312 0 : return NS_ERROR_NOT_IMPLEMENTED;
1313 :
1314 0 : return CreateSessionInternal(clientID, storagePolicy, streamBased, result);
1315 : }
1316 :
1317 : nsresult
1318 0 : nsCacheService::CreateSessionInternal(const char * clientID,
1319 : nsCacheStoragePolicy storagePolicy,
1320 : bool streamBased,
1321 : nsICacheSession **result)
1322 : {
1323 : RefPtr<nsCacheSession> session =
1324 0 : new nsCacheSession(clientID, storagePolicy, streamBased);
1325 0 : session.forget(result);
1326 :
1327 0 : return NS_OK;
1328 : }
1329 :
1330 :
1331 : nsresult
1332 0 : nsCacheService::EvictEntriesForSession(nsCacheSession * session)
1333 : {
1334 0 : NS_ASSERTION(gService, "nsCacheService::gService is null.");
1335 0 : return gService->EvictEntriesForClient(session->ClientID()->get(),
1336 0 : session->StoragePolicy());
1337 : }
1338 :
1339 : namespace {
1340 :
1341 0 : class EvictionNotifierRunnable : public Runnable
1342 : {
1343 : public:
1344 0 : explicit EvictionNotifierRunnable(nsISupports* aSubject)
1345 0 : : mozilla::Runnable("EvictionNotifierRunnable")
1346 0 : , mSubject(aSubject)
1347 : {
1348 0 : }
1349 :
1350 : NS_DECL_NSIRUNNABLE
1351 :
1352 : private:
1353 : nsCOMPtr<nsISupports> mSubject;
1354 : };
1355 :
1356 : NS_IMETHODIMP
1357 0 : EvictionNotifierRunnable::Run()
1358 : {
1359 : nsCOMPtr<nsIObserverService> obsSvc =
1360 0 : mozilla::services::GetObserverService();
1361 0 : if (obsSvc) {
1362 0 : obsSvc->NotifyObservers(mSubject,
1363 : NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
1364 0 : nullptr);
1365 : }
1366 0 : return NS_OK;
1367 : }
1368 :
1369 : } // namespace
1370 :
1371 : nsresult
1372 0 : nsCacheService::EvictEntriesForClient(const char * clientID,
1373 : nsCacheStoragePolicy storagePolicy)
1374 : {
1375 : RefPtr<EvictionNotifierRunnable> r =
1376 0 : new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
1377 0 : NS_DispatchToMainThread(r);
1378 :
1379 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
1380 0 : nsresult res = NS_OK;
1381 :
1382 0 : if (storagePolicy == nsICache::STORE_ANYWHERE ||
1383 : storagePolicy == nsICache::STORE_ON_DISK) {
1384 :
1385 0 : if (mEnableDiskDevice) {
1386 0 : nsresult rv = NS_OK;
1387 0 : if (!mDiskDevice)
1388 0 : rv = CreateDiskDevice();
1389 0 : if (mDiskDevice)
1390 0 : rv = mDiskDevice->EvictEntries(clientID);
1391 0 : if (NS_FAILED(rv))
1392 0 : res = rv;
1393 : }
1394 : }
1395 :
1396 : // Only clear the offline cache if it has been specifically asked for.
1397 0 : if (storagePolicy == nsICache::STORE_OFFLINE) {
1398 0 : if (mEnableOfflineDevice) {
1399 0 : nsresult rv = NS_OK;
1400 0 : if (!mOfflineDevice)
1401 0 : rv = CreateOfflineDevice();
1402 0 : if (mOfflineDevice)
1403 0 : rv = mOfflineDevice->EvictEntries(clientID);
1404 0 : if (NS_FAILED(rv))
1405 0 : res = rv;
1406 : }
1407 : }
1408 :
1409 0 : if (storagePolicy == nsICache::STORE_ANYWHERE ||
1410 : storagePolicy == nsICache::STORE_IN_MEMORY) {
1411 : // If there is no memory device, there is no need to evict it...
1412 0 : if (mMemoryDevice) {
1413 0 : nsresult rv = mMemoryDevice->EvictEntries(clientID);
1414 0 : if (NS_FAILED(rv))
1415 0 : res = rv;
1416 : }
1417 : }
1418 :
1419 0 : return res;
1420 : }
1421 :
1422 :
1423 : nsresult
1424 0 : nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
1425 : bool * result)
1426 : {
1427 0 : if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
1428 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
1429 :
1430 0 : *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
1431 0 : return NS_OK;
1432 : }
1433 :
1434 :
1435 : nsresult
1436 0 : nsCacheService::DoomEntry(nsCacheSession *session,
1437 : const nsACString &key,
1438 : nsICacheListener *listener)
1439 : {
1440 0 : CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
1441 : session, PromiseFlatCString(key).get()));
1442 0 : if (!gService || !gService->mInitialized)
1443 0 : return NS_ERROR_NOT_INITIALIZED;
1444 :
1445 0 : return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
1446 : }
1447 :
1448 :
1449 : bool
1450 0 : nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
1451 : {
1452 0 : if (gService->mEnableMemoryDevice &&
1453 0 : (storagePolicy == nsICache::STORE_ANYWHERE ||
1454 : storagePolicy == nsICache::STORE_IN_MEMORY)) {
1455 0 : return true;
1456 : }
1457 0 : if (gService->mEnableDiskDevice &&
1458 0 : (storagePolicy == nsICache::STORE_ANYWHERE ||
1459 : storagePolicy == nsICache::STORE_ON_DISK)) {
1460 0 : return true;
1461 : }
1462 0 : if (gService->mEnableOfflineDevice &&
1463 : storagePolicy == nsICache::STORE_OFFLINE) {
1464 0 : return true;
1465 : }
1466 :
1467 0 : return false;
1468 : }
1469 :
1470 0 : NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
1471 : {
1472 0 : if (net::CacheObserver::UseNewCache())
1473 0 : return NS_ERROR_NOT_IMPLEMENTED;
1474 :
1475 0 : return VisitEntriesInternal(visitor);
1476 : }
1477 :
1478 0 : nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor *visitor)
1479 : {
1480 0 : NS_ENSURE_ARG_POINTER(visitor);
1481 :
1482 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
1483 :
1484 0 : if (!(mEnableDiskDevice || mEnableMemoryDevice))
1485 0 : return NS_ERROR_NOT_AVAILABLE;
1486 :
1487 : // XXX record the fact that a visitation is in progress,
1488 : // XXX i.e. keep list of visitors in progress.
1489 :
1490 0 : nsresult rv = NS_OK;
1491 : // If there is no memory device, there are then also no entries to visit...
1492 0 : if (mMemoryDevice) {
1493 0 : rv = mMemoryDevice->Visit(visitor);
1494 0 : if (NS_FAILED(rv)) return rv;
1495 : }
1496 :
1497 0 : if (mEnableDiskDevice) {
1498 0 : if (!mDiskDevice) {
1499 0 : rv = CreateDiskDevice();
1500 0 : if (NS_FAILED(rv)) return rv;
1501 : }
1502 0 : rv = mDiskDevice->Visit(visitor);
1503 0 : if (NS_FAILED(rv)) return rv;
1504 : }
1505 :
1506 0 : if (mEnableOfflineDevice) {
1507 0 : if (!mOfflineDevice) {
1508 0 : rv = CreateOfflineDevice();
1509 0 : if (NS_FAILED(rv)) return rv;
1510 : }
1511 0 : rv = mOfflineDevice->Visit(visitor);
1512 0 : if (NS_FAILED(rv)) return rv;
1513 : }
1514 :
1515 : // XXX notify any shutdown process that visitation is complete for THIS visitor.
1516 : // XXX keep queue of visitors
1517 :
1518 0 : return NS_OK;
1519 : }
1520 :
1521 0 : void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification()
1522 : {
1523 0 : MOZ_ASSERT(NS_IsMainThread());
1524 0 : nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
1525 0 : if (obsvc) {
1526 0 : obsvc->NotifyObservers(nullptr,
1527 : "network-clear-cache-stored-anywhere",
1528 0 : nullptr);
1529 : }
1530 0 : }
1531 :
1532 0 : NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
1533 : {
1534 0 : if (net::CacheObserver::UseNewCache())
1535 0 : return NS_ERROR_NOT_IMPLEMENTED;
1536 :
1537 0 : return EvictEntriesInternal(storagePolicy);
1538 : }
1539 :
1540 0 : nsresult nsCacheService::EvictEntriesInternal(nsCacheStoragePolicy storagePolicy)
1541 : {
1542 0 : if (storagePolicy == nsICache::STORE_ANYWHERE) {
1543 : // if not called on main thread, dispatch the notification to the main thread to notify observers
1544 0 : if (!NS_IsMainThread()) {
1545 0 : nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
1546 : "nsCacheService::FireClearNetworkCacheStoredAnywhereNotification",
1547 : this,
1548 0 : &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
1549 0 : NS_DispatchToMainThread(event);
1550 : } else {
1551 : // else you're already on main thread - notify observers
1552 0 : FireClearNetworkCacheStoredAnywhereNotification();
1553 : }
1554 : }
1555 0 : return EvictEntriesForClient(nullptr, storagePolicy);
1556 : }
1557 :
1558 1 : NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
1559 : {
1560 1 : NS_ENSURE_ARG_POINTER(aCacheIOTarget);
1561 :
1562 : // Because mCacheIOThread can only be changed on the main thread, it can be
1563 : // read from the main thread without the lock. This is useful to prevent
1564 : // blocking the main thread on other cache operations.
1565 1 : if (!NS_IsMainThread()) {
1566 0 : Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
1567 : }
1568 :
1569 : nsresult rv;
1570 1 : if (mCacheIOThread) {
1571 1 : NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
1572 1 : rv = NS_OK;
1573 : } else {
1574 0 : *aCacheIOTarget = nullptr;
1575 0 : rv = NS_ERROR_NOT_AVAILABLE;
1576 : }
1577 :
1578 1 : if (!NS_IsMainThread()) {
1579 0 : Unlock();
1580 : }
1581 :
1582 1 : return rv;
1583 : }
1584 :
1585 0 : NS_IMETHODIMP nsCacheService::GetLockHeldTime(double *aLockHeldTime)
1586 : {
1587 0 : MutexAutoLock lock(mTimeStampLock);
1588 :
1589 0 : if (mLockAcquiredTimeStamp.IsNull()) {
1590 0 : *aLockHeldTime = 0.0;
1591 : }
1592 : else {
1593 0 : *aLockHeldTime =
1594 0 : (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
1595 : }
1596 :
1597 0 : return NS_OK;
1598 : }
1599 :
1600 : /**
1601 : * Internal Methods
1602 : */
1603 : nsresult
1604 0 : nsCacheService::CreateDiskDevice()
1605 : {
1606 0 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1607 0 : if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
1608 0 : if (mDiskDevice) return NS_OK;
1609 :
1610 0 : mDiskDevice = new nsDiskCacheDevice;
1611 0 : if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
1612 :
1613 : // set the preferences
1614 0 : mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
1615 0 : mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
1616 0 : mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
1617 :
1618 0 : nsresult rv = mDiskDevice->Init();
1619 0 : if (NS_FAILED(rv)) {
1620 : #if DEBUG
1621 0 : printf("###\n");
1622 0 : printf("### mDiskDevice->Init() failed (0x%.8x)\n",
1623 0 : static_cast<uint32_t>(rv));
1624 0 : printf("### - disabling disk cache for this session.\n");
1625 0 : printf("###\n");
1626 : #endif
1627 0 : mEnableDiskDevice = false;
1628 0 : delete mDiskDevice;
1629 0 : mDiskDevice = nullptr;
1630 0 : return rv;
1631 : }
1632 :
1633 0 : NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
1634 :
1635 : // Disk device is usually created during the startup. Delay smart size
1636 : // calculation to avoid possible massive IO caused by eviction of entries
1637 : // in case the new smart size is smaller than current cache usage.
1638 0 : mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1639 0 : if (NS_SUCCEEDED(rv)) {
1640 0 : rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
1641 : 1000*60*3,
1642 0 : nsITimer::TYPE_ONE_SHOT);
1643 0 : if (NS_FAILED(rv)) {
1644 0 : NS_WARNING("Failed to post smart size timer");
1645 0 : mSmartSizeTimer = nullptr;
1646 : }
1647 : } else {
1648 0 : NS_WARNING("Can't create smart size timer");
1649 : }
1650 : // Ignore state of the timer and return success since the purpose of the
1651 : // method (create the disk-device) has been fulfilled
1652 :
1653 0 : return NS_OK;
1654 : }
1655 :
1656 : // Runnable sent from cache thread to main thread
1657 0 : class nsDisableOldMaxSmartSizePrefEvent: public Runnable
1658 : {
1659 : public:
1660 0 : nsDisableOldMaxSmartSizePrefEvent()
1661 0 : : mozilla::Runnable("nsDisableOldMaxSmartSizePrefEvent")
1662 : {
1663 0 : }
1664 :
1665 0 : NS_IMETHOD Run() override
1666 : {
1667 : // Main thread may have already called nsCacheService::Shutdown
1668 0 : if (!nsCacheService::IsInitialized())
1669 0 : return NS_ERROR_NOT_AVAILABLE;
1670 :
1671 0 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
1672 0 : if (!branch) {
1673 0 : return NS_ERROR_NOT_AVAILABLE;
1674 : }
1675 :
1676 : nsresult rv =
1677 0 : branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
1678 0 : if (NS_FAILED(rv)) {
1679 0 : NS_WARNING("Failed to disable old max smart size");
1680 0 : return rv;
1681 : }
1682 :
1683 : // It is safe to call SetDiskSmartSize_Locked() without holding the lock
1684 : // when we are on main thread and nsCacheService is initialized.
1685 0 : nsCacheService::gService->SetDiskSmartSize_Locked();
1686 :
1687 0 : if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch,
1688 : false)) {
1689 0 : rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
1690 0 : if (NS_FAILED(rv)) {
1691 0 : NS_WARNING("Failed to set cache capacity pref");
1692 : }
1693 : }
1694 :
1695 0 : return NS_OK;
1696 : }
1697 : };
1698 :
1699 : void
1700 0 : nsCacheService::MarkStartingFresh()
1701 : {
1702 0 : if (!gService || !gService->mObserver->ShouldUseOldMaxSmartSize()) {
1703 : // Already using new max, nothing to do here
1704 0 : return;
1705 : }
1706 :
1707 0 : gService->mObserver->SetUseNewMaxSmartSize(true);
1708 :
1709 : // We always dispatch an event here because we don't want to deal with lock
1710 : // reentrance issues.
1711 0 : NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
1712 : }
1713 :
1714 : nsresult
1715 0 : nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
1716 : {
1717 0 : if (!mOfflineDevice) {
1718 0 : nsresult rv = CreateOfflineDevice();
1719 0 : NS_ENSURE_SUCCESS(rv, rv);
1720 : }
1721 :
1722 0 : NS_ADDREF(*aDevice = mOfflineDevice);
1723 0 : return NS_OK;
1724 : }
1725 :
1726 : nsresult
1727 0 : nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
1728 : int32_t aQuota,
1729 : nsOfflineCacheDevice **aDevice)
1730 : {
1731 : nsresult rv;
1732 :
1733 0 : nsAutoString profilePath;
1734 0 : rv = aProfileDir->GetPath(profilePath);
1735 0 : NS_ENSURE_SUCCESS(rv, rv);
1736 :
1737 0 : if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
1738 0 : rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
1739 0 : NS_ENSURE_SUCCESS(rv, rv);
1740 :
1741 0 : (*aDevice)->SetAutoShutdown();
1742 0 : mCustomOfflineDevices.Put(profilePath, *aDevice);
1743 : }
1744 :
1745 0 : return NS_OK;
1746 : }
1747 :
1748 : nsresult
1749 0 : nsCacheService::CreateOfflineDevice()
1750 : {
1751 0 : CACHE_LOG_INFO(("Creating default offline device"));
1752 :
1753 0 : if (mOfflineDevice) return NS_OK;
1754 0 : if (!nsCacheService::IsInitialized()) {
1755 0 : return NS_ERROR_NOT_AVAILABLE;
1756 : }
1757 :
1758 0 : nsresult rv = CreateCustomOfflineDevice(
1759 0 : mObserver->OfflineCacheParentDirectory(),
1760 0 : mObserver->OfflineCacheCapacity(),
1761 0 : &mOfflineDevice);
1762 0 : NS_ENSURE_SUCCESS(rv, rv);
1763 :
1764 0 : return NS_OK;
1765 : }
1766 :
1767 : nsresult
1768 0 : nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
1769 : int32_t aQuota,
1770 : nsOfflineCacheDevice **aDevice)
1771 : {
1772 0 : NS_ENSURE_ARG(aProfileDir);
1773 :
1774 0 : if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
1775 0 : nsAutoCString profilePath;
1776 0 : aProfileDir->GetNativePath(profilePath);
1777 0 : CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
1778 : profilePath.BeginReading(), aQuota));
1779 : }
1780 :
1781 0 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1782 0 : if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
1783 :
1784 0 : *aDevice = new nsOfflineCacheDevice;
1785 :
1786 0 : NS_ADDREF(*aDevice);
1787 :
1788 : // set the preferences
1789 0 : (*aDevice)->SetCacheParentDirectory(aProfileDir);
1790 0 : (*aDevice)->SetCapacity(aQuota);
1791 :
1792 0 : nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
1793 0 : if (NS_FAILED(rv)) {
1794 0 : CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8" PRIx32 ")\n",
1795 : static_cast<uint32_t>(rv)));
1796 0 : CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
1797 :
1798 0 : NS_RELEASE(*aDevice);
1799 : }
1800 0 : return rv;
1801 : }
1802 :
1803 : nsresult
1804 0 : nsCacheService::CreateMemoryDevice()
1805 : {
1806 0 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1807 0 : if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
1808 0 : if (mMemoryDevice) return NS_OK;
1809 :
1810 0 : mMemoryDevice = new nsMemoryCacheDevice;
1811 0 : if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
1812 :
1813 : // set preference
1814 0 : int32_t capacity = mObserver->MemoryCacheCapacity();
1815 0 : CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
1816 0 : mMemoryDevice->SetCapacity(capacity);
1817 0 : mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
1818 :
1819 0 : nsresult rv = mMemoryDevice->Init();
1820 0 : if (NS_FAILED(rv)) {
1821 0 : NS_WARNING("Initialization of Memory Cache failed.");
1822 0 : delete mMemoryDevice;
1823 0 : mMemoryDevice = nullptr;
1824 : }
1825 :
1826 0 : return rv;
1827 : }
1828 :
1829 : nsresult
1830 0 : nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
1831 : {
1832 0 : nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
1833 0 : if (!profileDir)
1834 0 : return NS_ERROR_UNEXPECTED;
1835 :
1836 0 : nsAutoString profilePath;
1837 0 : nsresult rv = profileDir->GetPath(profilePath);
1838 0 : NS_ENSURE_SUCCESS(rv, rv);
1839 :
1840 0 : mCustomOfflineDevices.Remove(profilePath);
1841 0 : return NS_OK;
1842 : }
1843 :
1844 : nsresult
1845 0 : nsCacheService::CreateRequest(nsCacheSession * session,
1846 : const nsACString & clientKey,
1847 : nsCacheAccessMode accessRequested,
1848 : bool blockingMode,
1849 : nsICacheListener * listener,
1850 : nsCacheRequest ** request)
1851 : {
1852 0 : NS_ASSERTION(request, "CreateRequest: request is null");
1853 :
1854 0 : nsAutoCString key(*session->ClientID());
1855 0 : key.Append(':');
1856 0 : key.Append(clientKey);
1857 :
1858 0 : if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
1859 :
1860 : // create request
1861 0 : *request = new nsCacheRequest(key, listener, accessRequested,
1862 0 : blockingMode, session);
1863 :
1864 0 : if (!listener) return NS_OK; // we're sync, we're done.
1865 :
1866 : // get the request's thread
1867 0 : (*request)->mEventTarget = GetCurrentThreadEventTarget();
1868 :
1869 0 : return NS_OK;
1870 : }
1871 :
1872 :
1873 0 : class nsCacheListenerEvent : public Runnable
1874 : {
1875 : public:
1876 0 : nsCacheListenerEvent(nsICacheListener* listener,
1877 : nsICacheEntryDescriptor* descriptor,
1878 : nsCacheAccessMode accessGranted,
1879 : nsresult status)
1880 0 : : mozilla::Runnable("nsCacheListenerEvent")
1881 : , mListener(listener) // transfers reference
1882 : , mDescriptor(descriptor) // transfers reference (may be null)
1883 : , mAccessGranted(accessGranted)
1884 0 : , mStatus(status)
1885 : {
1886 0 : }
1887 :
1888 0 : NS_IMETHOD Run() override
1889 : {
1890 0 : mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
1891 :
1892 0 : NS_RELEASE(mListener);
1893 0 : NS_IF_RELEASE(mDescriptor);
1894 0 : return NS_OK;
1895 : }
1896 :
1897 : private:
1898 : // We explicitly leak mListener or mDescriptor if Run is not called
1899 : // because otherwise we cannot guarantee that they are destroyed on
1900 : // the right thread.
1901 :
1902 : nsICacheListener *mListener;
1903 : nsICacheEntryDescriptor *mDescriptor;
1904 : nsCacheAccessMode mAccessGranted;
1905 : nsresult mStatus;
1906 : };
1907 :
1908 :
1909 : nsresult
1910 0 : nsCacheService::NotifyListener(nsCacheRequest * request,
1911 : nsICacheEntryDescriptor * descriptor,
1912 : nsCacheAccessMode accessGranted,
1913 : nsresult status)
1914 : {
1915 0 : NS_ASSERTION(request->mEventTarget, "no thread set in async request!");
1916 :
1917 : // Swap ownership, and release listener on target thread...
1918 0 : nsICacheListener *listener = request->mListener;
1919 0 : request->mListener = nullptr;
1920 :
1921 : nsCOMPtr<nsIRunnable> ev =
1922 : new nsCacheListenerEvent(listener, descriptor,
1923 0 : accessGranted, status);
1924 0 : if (!ev) {
1925 : // Better to leak listener and descriptor if we fail because we don't
1926 : // want to destroy them inside the cache service lock or on potentially
1927 : // the wrong thread.
1928 0 : return NS_ERROR_OUT_OF_MEMORY;
1929 : }
1930 :
1931 0 : return request->mEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
1932 : }
1933 :
1934 :
1935 : nsresult
1936 0 : nsCacheService::ProcessRequest(nsCacheRequest * request,
1937 : bool calledFromOpenCacheEntry,
1938 : nsICacheEntryDescriptor ** result)
1939 : {
1940 : // !!! must be called with mLock held !!!
1941 : nsresult rv;
1942 0 : nsCacheEntry * entry = nullptr;
1943 0 : nsCacheEntry * doomedEntry = nullptr;
1944 0 : nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1945 0 : if (result) *result = nullptr;
1946 :
1947 : while(1) { // Activate entry loop
1948 0 : rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
1949 0 : if (NS_FAILED(rv)) break;
1950 :
1951 0 : while(1) { // Request Access loop
1952 0 : NS_ASSERTION(entry, "no entry in Request Access loop!");
1953 : // entry->RequestAccess queues request on entry
1954 0 : rv = entry->RequestAccess(request, &accessGranted);
1955 0 : if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1956 :
1957 0 : if (request->IsBlocking()) {
1958 0 : if (request->mListener) {
1959 : // async exits - validate, doom, or close will resume
1960 0 : return rv;
1961 : }
1962 :
1963 : // XXX this is probably wrong...
1964 0 : Unlock();
1965 0 : rv = request->WaitForValidation();
1966 0 : Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
1967 : }
1968 :
1969 0 : PR_REMOVE_AND_INIT_LINK(request);
1970 0 : if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1971 : // okay, we're ready to process this request, request access again
1972 : }
1973 0 : if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1974 :
1975 0 : if (entry->IsNotInUse()) {
1976 : // this request was the last one keeping it around, so get rid of it
1977 0 : DeactivateEntry(entry);
1978 : }
1979 : // loop back around to look for another entry
1980 : }
1981 :
1982 0 : if (NS_SUCCEEDED(rv) && request->mProfileDir) {
1983 : // Custom cache directory has been demanded. Preset the cache device.
1984 0 : if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
1985 : // Failsafe check: this is implemented only for offline cache atm.
1986 0 : rv = NS_ERROR_FAILURE;
1987 : } else {
1988 0 : RefPtr<nsOfflineCacheDevice> customCacheDevice;
1989 0 : rv = GetCustomOfflineDevice(request->mProfileDir, -1,
1990 0 : getter_AddRefs(customCacheDevice));
1991 0 : if (NS_SUCCEEDED(rv))
1992 0 : entry->SetCustomCacheDevice(customCacheDevice);
1993 : }
1994 : }
1995 :
1996 0 : nsICacheEntryDescriptor *descriptor = nullptr;
1997 :
1998 0 : if (NS_SUCCEEDED(rv))
1999 0 : rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
2000 :
2001 : // If doomedEntry is set, ActivatEntry() doomed an existing entry and
2002 : // created a new one for that cache-key. However, any pending requests
2003 : // on the doomed entry were not processed and we need to do that here.
2004 : // This must be done after adding the created entry to list of active
2005 : // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
2006 : // (see bug ##561313). It is also important to do this after creating a
2007 : // descriptor for this request, or some other request may end up being
2008 : // executed first for the newly created entry.
2009 : // Finally, it is worth to emphasize that if doomedEntry is set,
2010 : // ActivateEntry() created a new entry for the request, which will be
2011 : // initialized by RequestAccess() and they both should have returned NS_OK.
2012 0 : if (doomedEntry) {
2013 0 : (void) ProcessPendingRequests(doomedEntry);
2014 0 : if (doomedEntry->IsNotInUse())
2015 0 : DeactivateEntry(doomedEntry);
2016 0 : doomedEntry = nullptr;
2017 : }
2018 :
2019 0 : if (request->mListener) { // Asynchronous
2020 :
2021 0 : if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
2022 0 : return rv; // skip notifying listener, just return rv to caller
2023 :
2024 : // call listener to report error or descriptor
2025 0 : nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
2026 0 : if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
2027 0 : rv = rv2; // trigger delete request
2028 : }
2029 : } else { // Synchronous
2030 0 : *result = descriptor;
2031 : }
2032 0 : return rv;
2033 : }
2034 :
2035 :
2036 : nsresult
2037 0 : nsCacheService::OpenCacheEntry(nsCacheSession * session,
2038 : const nsACString & key,
2039 : nsCacheAccessMode accessRequested,
2040 : bool blockingMode,
2041 : nsICacheListener * listener,
2042 : nsICacheEntryDescriptor ** result)
2043 : {
2044 0 : CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
2045 : session, PromiseFlatCString(key).get(), accessRequested,
2046 : blockingMode));
2047 0 : if (result)
2048 0 : *result = nullptr;
2049 :
2050 0 : if (!gService || !gService->mInitialized)
2051 0 : return NS_ERROR_NOT_INITIALIZED;
2052 :
2053 0 : nsCacheRequest * request = nullptr;
2054 :
2055 0 : nsresult rv = gService->CreateRequest(session,
2056 : key,
2057 : accessRequested,
2058 : blockingMode,
2059 : listener,
2060 0 : &request);
2061 0 : if (NS_FAILED(rv)) return rv;
2062 :
2063 0 : CACHE_LOG_DEBUG(("Created request %p\n", request));
2064 :
2065 : // Process the request on the background thread if we are on the main thread
2066 : // and the the request is asynchronous
2067 0 : if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
2068 : nsCOMPtr<nsIRunnable> ev =
2069 0 : new nsProcessRequestEvent(request);
2070 0 : rv = DispatchToCacheIOThread(ev);
2071 :
2072 : // delete request if we didn't post the event
2073 0 : if (NS_FAILED(rv))
2074 0 : delete request;
2075 : }
2076 : else {
2077 :
2078 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
2079 0 : rv = gService->ProcessRequest(request, true, result);
2080 :
2081 : // delete requests that have completed
2082 0 : if (!(listener && blockingMode &&
2083 : (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
2084 0 : delete request;
2085 : }
2086 :
2087 0 : return rv;
2088 : }
2089 :
2090 :
2091 : nsresult
2092 0 : nsCacheService::ActivateEntry(nsCacheRequest * request,
2093 : nsCacheEntry ** result,
2094 : nsCacheEntry ** doomedEntry)
2095 : {
2096 0 : CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
2097 0 : if (!mInitialized || mClearingEntries)
2098 0 : return NS_ERROR_NOT_AVAILABLE;
2099 :
2100 0 : nsresult rv = NS_OK;
2101 :
2102 0 : NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
2103 0 : if (result) *result = nullptr;
2104 0 : if (doomedEntry) *doomedEntry = nullptr;
2105 0 : if ((!request) || (!result) || (!doomedEntry))
2106 0 : return NS_ERROR_NULL_POINTER;
2107 :
2108 : // check if the request can be satisfied
2109 0 : if (!mEnableMemoryDevice && !request->IsStreamBased())
2110 0 : return NS_ERROR_FAILURE;
2111 0 : if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
2112 0 : return NS_ERROR_FAILURE;
2113 :
2114 : // search active entries (including those not bound to device)
2115 0 : nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
2116 0 : CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
2117 :
2118 0 : if (!entry) {
2119 : // search cache devices for entry
2120 0 : bool collision = false;
2121 0 : entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
2122 0 : CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
2123 : request, entry));
2124 : // When there is a hashkey collision just refuse to cache it...
2125 0 : if (collision) return NS_ERROR_CACHE_IN_USE;
2126 :
2127 0 : if (entry) entry->MarkInitialized();
2128 : } else {
2129 0 : NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
2130 : }
2131 :
2132 0 : if (entry) {
2133 0 : ++mCacheHits;
2134 0 : entry->Fetched();
2135 : } else {
2136 0 : ++mCacheMisses;
2137 : }
2138 :
2139 0 : if (entry &&
2140 0 : ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
2141 0 : ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
2142 0 : (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
2143 0 : request->WillDoomEntriesIfExpired()))))
2144 :
2145 : {
2146 : // this is FORCE-WRITE request or the entry has expired
2147 : // we doom entry without processing pending requests, but store it in
2148 : // doomedEntry which causes pending requests to be processed below
2149 0 : rv = DoomEntry_Internal(entry, false);
2150 0 : *doomedEntry = entry;
2151 0 : if (NS_FAILED(rv)) {
2152 : // XXX what to do? Increment FailedDooms counter?
2153 : }
2154 0 : entry = nullptr;
2155 : }
2156 :
2157 0 : if (!entry) {
2158 0 : if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
2159 : // this is a READ-ONLY request
2160 0 : rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
2161 0 : goto error;
2162 : }
2163 :
2164 0 : entry = new nsCacheEntry(request->mKey,
2165 0 : request->IsStreamBased(),
2166 0 : request->StoragePolicy());
2167 0 : if (!entry)
2168 0 : return NS_ERROR_OUT_OF_MEMORY;
2169 :
2170 0 : if (request->IsPrivate())
2171 0 : entry->MarkPrivate();
2172 :
2173 0 : entry->Fetched();
2174 0 : ++mTotalEntries;
2175 :
2176 : // XXX we could perform an early bind in some cases based on storage policy
2177 : }
2178 :
2179 0 : if (!entry->IsActive()) {
2180 0 : rv = mActiveEntries.AddEntry(entry);
2181 0 : if (NS_FAILED(rv)) goto error;
2182 0 : CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
2183 0 : entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
2184 : }
2185 0 : *result = entry;
2186 0 : return NS_OK;
2187 :
2188 : error:
2189 0 : *result = nullptr;
2190 0 : delete entry;
2191 0 : return rv;
2192 : }
2193 :
2194 :
2195 : nsCacheEntry *
2196 0 : nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
2197 : {
2198 0 : Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
2199 0 : nsCacheEntry * entry = nullptr;
2200 :
2201 0 : CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
2202 :
2203 0 : *collision = false;
2204 0 : if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
2205 : // If there is no memory device, then there is nothing to search...
2206 0 : if (mMemoryDevice) {
2207 0 : entry = mMemoryDevice->FindEntry(key, collision);
2208 0 : CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
2209 : "collision: %d\n", key->get(), entry, *collision));
2210 : }
2211 : }
2212 :
2213 0 : if (!entry &&
2214 0 : ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
2215 :
2216 0 : if (mEnableDiskDevice) {
2217 0 : if (!mDiskDevice) {
2218 0 : nsresult rv = CreateDiskDevice();
2219 0 : if (NS_FAILED(rv))
2220 0 : return nullptr;
2221 : }
2222 :
2223 0 : entry = mDiskDevice->FindEntry(key, collision);
2224 : }
2225 : }
2226 :
2227 0 : if (!entry && (policy == nsICache::STORE_OFFLINE ||
2228 0 : (policy == nsICache::STORE_ANYWHERE &&
2229 0 : gIOService->IsOffline()))) {
2230 :
2231 0 : if (mEnableOfflineDevice) {
2232 0 : if (!mOfflineDevice) {
2233 0 : nsresult rv = CreateOfflineDevice();
2234 0 : if (NS_FAILED(rv))
2235 0 : return nullptr;
2236 : }
2237 :
2238 0 : entry = mOfflineDevice->FindEntry(key, collision);
2239 : }
2240 : }
2241 :
2242 0 : return entry;
2243 : }
2244 :
2245 :
2246 : nsCacheDevice *
2247 0 : nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
2248 : {
2249 0 : nsCacheDevice * device = entry->CacheDevice();
2250 : // return device if found, possibly null if the entry is doomed i.e prevent
2251 : // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
2252 0 : if (device || entry->IsDoomed()) return device;
2253 :
2254 0 : int64_t predictedDataSize = entry->PredictedDataSize();
2255 0 : if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
2256 : // this is the default
2257 0 : if (!mDiskDevice) {
2258 0 : (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
2259 : }
2260 :
2261 0 : if (mDiskDevice) {
2262 : // Bypass the cache if Content-Length says the entry will be too big
2263 0 : if (predictedDataSize != -1 &&
2264 0 : mDiskDevice->EntryIsTooBig(predictedDataSize)) {
2265 0 : DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2266 0 : NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2267 0 : return nullptr;
2268 : }
2269 :
2270 0 : entry->MarkBinding(); // enter state of binding
2271 0 : nsresult rv = mDiskDevice->BindEntry(entry);
2272 0 : entry->ClearBinding(); // exit state of binding
2273 0 : if (NS_SUCCEEDED(rv))
2274 0 : device = mDiskDevice;
2275 : }
2276 : }
2277 :
2278 : // if we can't use mDiskDevice, try mMemoryDevice
2279 0 : if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
2280 0 : if (!mMemoryDevice) {
2281 0 : (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
2282 : }
2283 0 : if (mMemoryDevice) {
2284 : // Bypass the cache if Content-Length says entry will be too big
2285 0 : if (predictedDataSize != -1 &&
2286 0 : mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
2287 0 : DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2288 0 : NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2289 0 : return nullptr;
2290 : }
2291 :
2292 0 : entry->MarkBinding(); // enter state of binding
2293 0 : nsresult rv = mMemoryDevice->BindEntry(entry);
2294 0 : entry->ClearBinding(); // exit state of binding
2295 0 : if (NS_SUCCEEDED(rv))
2296 0 : device = mMemoryDevice;
2297 : }
2298 : }
2299 :
2300 0 : if (!device && entry->IsStreamData() &&
2301 0 : entry->IsAllowedOffline() && mEnableOfflineDevice) {
2302 0 : if (!mOfflineDevice) {
2303 0 : (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
2304 : }
2305 :
2306 0 : device = entry->CustomCacheDevice()
2307 0 : ? entry->CustomCacheDevice()
2308 : : mOfflineDevice;
2309 :
2310 0 : if (device) {
2311 0 : entry->MarkBinding();
2312 0 : nsresult rv = device->BindEntry(entry);
2313 0 : entry->ClearBinding();
2314 0 : if (NS_FAILED(rv))
2315 0 : device = nullptr;
2316 : }
2317 : }
2318 :
2319 0 : if (device)
2320 0 : entry->SetCacheDevice(device);
2321 0 : return device;
2322 : }
2323 :
2324 : nsresult
2325 0 : nsCacheService::DoomEntry(nsCacheEntry * entry)
2326 : {
2327 0 : return gService->DoomEntry_Internal(entry, true);
2328 : }
2329 :
2330 :
2331 : nsresult
2332 0 : nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
2333 : bool doProcessPendingRequests)
2334 : {
2335 0 : if (entry->IsDoomed()) return NS_OK;
2336 :
2337 0 : CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
2338 0 : nsresult rv = NS_OK;
2339 0 : entry->MarkDoomed();
2340 :
2341 0 : NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
2342 0 : nsCacheDevice * device = entry->CacheDevice();
2343 0 : if (device) device->DoomEntry(entry);
2344 :
2345 0 : if (entry->IsActive()) {
2346 : // remove from active entries
2347 0 : mActiveEntries.RemoveEntry(entry);
2348 0 : CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
2349 0 : entry->MarkInactive();
2350 : }
2351 :
2352 : // put on doom list to wait for descriptors to close
2353 0 : NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
2354 0 : PR_APPEND_LINK(entry, &mDoomedEntries);
2355 :
2356 : // handle pending requests only if we're supposed to
2357 0 : if (doProcessPendingRequests) {
2358 : // tell pending requests to get on with their lives...
2359 0 : rv = ProcessPendingRequests(entry);
2360 :
2361 : // All requests have been removed, but there may still be open descriptors
2362 0 : if (entry->IsNotInUse()) {
2363 0 : DeactivateEntry(entry); // tell device to get rid of it
2364 : }
2365 : }
2366 0 : return rv;
2367 : }
2368 :
2369 :
2370 : void
2371 0 : nsCacheService::OnProfileShutdown()
2372 : {
2373 0 : if (!gService || !gService->mInitialized) {
2374 : // The cache service has been shut down, but someone is still holding
2375 : // a reference to it. Ignore this call.
2376 0 : return;
2377 : }
2378 :
2379 : {
2380 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2381 0 : gService->mClearingEntries = true;
2382 0 : gService->DoomActiveEntries(nullptr);
2383 : }
2384 :
2385 0 : gService->CloseAllStreams();
2386 :
2387 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2388 0 : gService->ClearDoomList();
2389 :
2390 : // Make sure to wait for any pending cache-operations before
2391 : // proceeding with destructive actions (bug #620660)
2392 0 : (void) SyncWithCacheIOThread();
2393 :
2394 0 : if (gService->mDiskDevice && gService->mEnableDiskDevice) {
2395 0 : gService->mDiskDevice->Shutdown();
2396 : }
2397 0 : gService->mEnableDiskDevice = false;
2398 :
2399 0 : if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
2400 0 : gService->mOfflineDevice->Shutdown();
2401 : }
2402 0 : for (auto iter = gService->mCustomOfflineDevices.Iter();
2403 0 : !iter.Done(); iter.Next()) {
2404 0 : iter.Data()->Shutdown();
2405 0 : iter.Remove();
2406 : }
2407 :
2408 0 : gService->mEnableOfflineDevice = false;
2409 :
2410 0 : if (gService->mMemoryDevice) {
2411 : // clear memory cache
2412 0 : gService->mMemoryDevice->EvictEntries(nullptr);
2413 : }
2414 :
2415 0 : gService->mClearingEntries = false;
2416 : }
2417 :
2418 :
2419 : void
2420 0 : nsCacheService::OnProfileChanged()
2421 : {
2422 0 : if (!gService) return;
2423 :
2424 0 : CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
2425 :
2426 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
2427 :
2428 0 : gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2429 0 : gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2430 0 : gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2431 :
2432 0 : if (gService->mDiskDevice) {
2433 0 : gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
2434 0 : gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
2435 :
2436 : // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
2437 0 : nsresult rv = gService->mDiskDevice->Init();
2438 0 : if (NS_FAILED(rv)) {
2439 0 : NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
2440 0 : gService->mEnableDiskDevice = false;
2441 : // XXX delete mDiskDevice?
2442 : }
2443 : }
2444 :
2445 0 : if (gService->mOfflineDevice) {
2446 0 : gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
2447 0 : gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
2448 :
2449 : // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
2450 0 : nsresult rv = gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
2451 0 : if (NS_FAILED(rv)) {
2452 0 : NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
2453 0 : gService->mEnableOfflineDevice = false;
2454 : // XXX delete mOfflineDevice?
2455 : }
2456 : }
2457 :
2458 : // If memoryDevice exists, reset its size to the new profile
2459 0 : if (gService->mMemoryDevice) {
2460 0 : if (gService->mEnableMemoryDevice) {
2461 : // make sure that capacity is reset to the right value
2462 0 : int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2463 0 : CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2464 : capacity));
2465 0 : gService->mMemoryDevice->SetCapacity(capacity);
2466 : } else {
2467 : // tell memory device to evict everything
2468 0 : CACHE_LOG_DEBUG(("memory device disabled\n"));
2469 0 : gService->mMemoryDevice->SetCapacity(0);
2470 : // Don't delete memory device, because some entries may be active still...
2471 : }
2472 : }
2473 : }
2474 :
2475 :
2476 : void
2477 0 : nsCacheService::SetDiskCacheEnabled(bool enabled)
2478 : {
2479 0 : if (!gService) return;
2480 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
2481 0 : gService->mEnableDiskDevice = enabled;
2482 : }
2483 :
2484 :
2485 : void
2486 0 : nsCacheService::SetDiskCacheCapacity(int32_t capacity)
2487 : {
2488 0 : if (!gService) return;
2489 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
2490 :
2491 0 : if (gService->mDiskDevice) {
2492 0 : gService->mDiskDevice->SetCapacity(capacity);
2493 : }
2494 :
2495 0 : gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2496 : }
2497 :
2498 : void
2499 0 : nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
2500 : {
2501 0 : if (!gService) return;
2502 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
2503 :
2504 0 : if (gService->mDiskDevice) {
2505 0 : gService->mDiskDevice->SetMaxEntrySize(maxSize);
2506 : }
2507 : }
2508 :
2509 : void
2510 0 : nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
2511 : {
2512 0 : if (!gService) return;
2513 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
2514 :
2515 0 : if (gService->mMemoryDevice) {
2516 0 : gService->mMemoryDevice->SetMaxEntrySize(maxSize);
2517 : }
2518 : }
2519 :
2520 : void
2521 0 : nsCacheService::SetOfflineCacheEnabled(bool enabled)
2522 : {
2523 0 : if (!gService) return;
2524 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
2525 0 : gService->mEnableOfflineDevice = enabled;
2526 : }
2527 :
2528 : void
2529 0 : nsCacheService::SetOfflineCacheCapacity(int32_t capacity)
2530 : {
2531 0 : if (!gService) return;
2532 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
2533 :
2534 0 : if (gService->mOfflineDevice) {
2535 0 : gService->mOfflineDevice->SetCapacity(capacity);
2536 : }
2537 :
2538 0 : gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2539 : }
2540 :
2541 :
2542 : void
2543 0 : nsCacheService::SetMemoryCache()
2544 : {
2545 0 : if (!gService) return;
2546 :
2547 0 : CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
2548 :
2549 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
2550 :
2551 0 : gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2552 :
2553 0 : if (gService->mEnableMemoryDevice) {
2554 0 : if (gService->mMemoryDevice) {
2555 0 : int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2556 : // make sure that capacity is reset to the right value
2557 0 : CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2558 : capacity));
2559 0 : gService->mMemoryDevice->SetCapacity(capacity);
2560 : }
2561 : } else {
2562 0 : if (gService->mMemoryDevice) {
2563 : // tell memory device to evict everything
2564 0 : CACHE_LOG_DEBUG(("memory device disabled\n"));
2565 0 : gService->mMemoryDevice->SetCapacity(0);
2566 : // Don't delete memory device, because some entries may be active still...
2567 : }
2568 : }
2569 : }
2570 :
2571 :
2572 : /******************************************************************************
2573 : * static methods for nsCacheEntryDescriptor
2574 : *****************************************************************************/
2575 : void
2576 0 : nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
2577 : {
2578 : // ask entry to remove descriptor
2579 0 : nsCacheEntry * entry = descriptor->CacheEntry();
2580 : bool doomEntry;
2581 0 : bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
2582 :
2583 0 : if (!entry->IsValid()) {
2584 0 : gService->ProcessPendingRequests(entry);
2585 : }
2586 :
2587 0 : if (doomEntry) {
2588 0 : gService->DoomEntry_Internal(entry, true);
2589 0 : return;
2590 : }
2591 :
2592 0 : if (!stillActive) {
2593 0 : gService->DeactivateEntry(entry);
2594 : }
2595 : }
2596 :
2597 :
2598 : nsresult
2599 0 : nsCacheService::GetFileForEntry(nsCacheEntry * entry,
2600 : nsIFile ** result)
2601 : {
2602 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2603 0 : if (!device) return NS_ERROR_UNEXPECTED;
2604 :
2605 0 : return device->GetFileForEntry(entry, result);
2606 : }
2607 :
2608 :
2609 : nsresult
2610 0 : nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
2611 : nsCacheAccessMode mode,
2612 : uint32_t offset,
2613 : nsIInputStream ** result)
2614 : {
2615 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2616 0 : if (!device) return NS_ERROR_UNEXPECTED;
2617 :
2618 0 : return device->OpenInputStreamForEntry(entry, mode, offset, result);
2619 : }
2620 :
2621 : nsresult
2622 0 : nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
2623 : nsCacheAccessMode mode,
2624 : uint32_t offset,
2625 : nsIOutputStream ** result)
2626 : {
2627 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2628 0 : if (!device) return NS_ERROR_UNEXPECTED;
2629 :
2630 0 : return device->OpenOutputStreamForEntry(entry, mode, offset, result);
2631 : }
2632 :
2633 :
2634 : nsresult
2635 0 : nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
2636 : {
2637 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2638 0 : if (!device) return NS_ERROR_UNEXPECTED;
2639 :
2640 0 : return device->OnDataSizeChange(entry, deltaSize);
2641 : }
2642 :
2643 : void
2644 0 : nsCacheService::LockAcquired()
2645 : {
2646 0 : MutexAutoLock lock(mTimeStampLock);
2647 0 : mLockAcquiredTimeStamp = TimeStamp::Now();
2648 0 : }
2649 :
2650 : void
2651 0 : nsCacheService::LockReleased()
2652 : {
2653 0 : MutexAutoLock lock(mTimeStampLock);
2654 0 : mLockAcquiredTimeStamp = TimeStamp();
2655 0 : }
2656 :
2657 : void
2658 0 : nsCacheService::Lock()
2659 : {
2660 0 : gService->mLock.Lock();
2661 0 : gService->LockAcquired();
2662 0 : }
2663 :
2664 : void
2665 0 : nsCacheService::Lock(mozilla::Telemetry::HistogramID mainThreadLockerID)
2666 : {
2667 : mozilla::Telemetry::HistogramID lockerID;
2668 : mozilla::Telemetry::HistogramID generalID;
2669 :
2670 0 : if (NS_IsMainThread()) {
2671 0 : lockerID = mainThreadLockerID;
2672 0 : generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
2673 : } else {
2674 0 : lockerID = mozilla::Telemetry::HistogramCount;
2675 0 : generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
2676 : }
2677 :
2678 0 : TimeStamp start(TimeStamp::Now());
2679 :
2680 0 : nsCacheService::Lock();
2681 :
2682 0 : TimeStamp stop(TimeStamp::Now());
2683 :
2684 : // Telemetry isn't thread safe on its own, but this is OK because we're
2685 : // protecting it with the cache lock.
2686 0 : if (lockerID != mozilla::Telemetry::HistogramCount) {
2687 0 : mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
2688 : }
2689 0 : mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
2690 0 : }
2691 :
2692 : void
2693 0 : nsCacheService::Unlock()
2694 : {
2695 0 : gService->mLock.AssertCurrentThreadOwns();
2696 :
2697 0 : nsTArray<nsISupports*> doomed;
2698 0 : doomed.SwapElements(gService->mDoomedObjects);
2699 :
2700 0 : gService->LockReleased();
2701 0 : gService->mLock.Unlock();
2702 :
2703 0 : for (uint32_t i = 0; i < doomed.Length(); ++i)
2704 0 : doomed[i]->Release();
2705 0 : }
2706 :
2707 : void
2708 0 : nsCacheService::ReleaseObject_Locked(nsISupports * obj,
2709 : nsIEventTarget * target)
2710 : {
2711 0 : gService->mLock.AssertCurrentThreadOwns();
2712 :
2713 : bool isCur;
2714 0 : if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
2715 0 : gService->mDoomedObjects.AppendElement(obj);
2716 : } else {
2717 : NS_ProxyRelease(
2718 0 : "nsCacheService::ReleaseObject_Locked::obj", target, dont_AddRef(obj));
2719 : }
2720 0 : }
2721 :
2722 :
2723 : nsresult
2724 0 : nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
2725 : {
2726 0 : entry->SetData(element);
2727 0 : entry->TouchData();
2728 0 : return NS_OK;
2729 : }
2730 :
2731 :
2732 : nsresult
2733 0 : nsCacheService::ValidateEntry(nsCacheEntry * entry)
2734 : {
2735 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2736 0 : if (!device) return NS_ERROR_UNEXPECTED;
2737 :
2738 0 : entry->MarkValid();
2739 0 : nsresult rv = gService->ProcessPendingRequests(entry);
2740 0 : NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
2741 : // XXX what else should be done?
2742 :
2743 0 : return rv;
2744 : }
2745 :
2746 :
2747 : int32_t
2748 0 : nsCacheService::CacheCompressionLevel()
2749 : {
2750 0 : int32_t level = gService->mObserver->CacheCompressionLevel();
2751 0 : return level;
2752 : }
2753 :
2754 :
2755 : void
2756 0 : nsCacheService::DeactivateEntry(nsCacheEntry * entry)
2757 : {
2758 0 : CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
2759 0 : nsresult rv = NS_OK;
2760 0 : NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
2761 0 : nsCacheDevice * device = nullptr;
2762 :
2763 0 : if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
2764 0 : if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
2765 :
2766 0 : if (entry->IsDoomed()) {
2767 : // remove from Doomed list
2768 0 : PR_REMOVE_AND_INIT_LINK(entry);
2769 0 : } else if (entry->IsActive()) {
2770 : // remove from active entries
2771 0 : mActiveEntries.RemoveEntry(entry);
2772 0 : CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
2773 : entry));
2774 0 : entry->MarkInactive();
2775 :
2776 : // bind entry if necessary to store meta-data
2777 0 : device = EnsureEntryHasDevice(entry);
2778 0 : if (!device) {
2779 0 : CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
2780 : "entry %p\n",
2781 : entry));
2782 0 : NS_WARNING("DeactivateEntry: unable to bind active entry\n");
2783 0 : return;
2784 : }
2785 : } else {
2786 : // if mInitialized == false,
2787 : // then we're shutting down and this state is okay.
2788 0 : NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
2789 : }
2790 :
2791 0 : device = entry->CacheDevice();
2792 0 : if (device) {
2793 0 : rv = device->DeactivateEntry(entry);
2794 0 : if (NS_FAILED(rv)) {
2795 : // increment deactivate failure count
2796 0 : ++mDeactivateFailures;
2797 : }
2798 : } else {
2799 : // increment deactivating unbound entry statistic
2800 0 : ++mDeactivatedUnboundEntries;
2801 0 : delete entry; // because no one else will
2802 : }
2803 : }
2804 :
2805 :
2806 : nsresult
2807 0 : nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
2808 : {
2809 0 : nsresult rv = NS_OK;
2810 0 : nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2811 : nsCacheRequest * nextRequest;
2812 0 : bool newWriter = false;
2813 :
2814 0 : CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
2815 : (entry->IsInitialized()?"" : "Un"),
2816 : (entry->IsDoomed()?"DOOMED" : ""),
2817 : (entry->IsValid()? "V":"Inv"), entry));
2818 :
2819 0 : if (request == &entry->mRequestQ) return NS_OK; // no queued requests
2820 :
2821 0 : if (!entry->IsDoomed() && entry->IsInvalid()) {
2822 : // 1st descriptor closed w/o MarkValid()
2823 0 : NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
2824 :
2825 : #if DEBUG
2826 : // verify no ACCESS_WRITE requests(shouldn't have any of these)
2827 0 : while (request != &entry->mRequestQ) {
2828 0 : NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
2829 : "ACCESS_WRITE request should have been given a new entry");
2830 0 : request = (nsCacheRequest *)PR_NEXT_LINK(request);
2831 : }
2832 0 : request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2833 : #endif
2834 : // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
2835 0 : while (request != &entry->mRequestQ) {
2836 0 : if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
2837 0 : newWriter = true;
2838 0 : CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
2839 0 : break;
2840 : }
2841 :
2842 0 : request = (nsCacheRequest *)PR_NEXT_LINK(request);
2843 : }
2844 :
2845 0 : if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
2846 0 : request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2847 :
2848 : // XXX what should we do if there are only READ requests in queue?
2849 : // XXX serialize their accesses, give them only read access, but force them to check validate flag?
2850 : // XXX or do readers simply presume the entry is valid
2851 : // See fix for bug #467392 below
2852 : }
2853 :
2854 0 : nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
2855 :
2856 0 : while (request != &entry->mRequestQ) {
2857 0 : nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
2858 0 : CACHE_LOG_DEBUG((" %sync request %p for %p\n",
2859 : (request->mListener?"As":"S"), request, entry));
2860 :
2861 0 : if (request->mListener) {
2862 :
2863 : // Async request
2864 0 : PR_REMOVE_AND_INIT_LINK(request);
2865 :
2866 0 : if (entry->IsDoomed()) {
2867 0 : rv = ProcessRequest(request, false, nullptr);
2868 0 : if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
2869 0 : rv = NS_OK;
2870 : else
2871 0 : delete request;
2872 :
2873 0 : if (NS_FAILED(rv)) {
2874 : // XXX what to do?
2875 : }
2876 0 : } else if (entry->IsValid() || newWriter) {
2877 0 : rv = entry->RequestAccess(request, &accessGranted);
2878 0 : NS_ASSERTION(NS_SUCCEEDED(rv),
2879 : "if entry is valid, RequestAccess must succeed.");
2880 : // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
2881 :
2882 : // entry->CreateDescriptor dequeues request, and queues descriptor
2883 0 : nsICacheEntryDescriptor *descriptor = nullptr;
2884 0 : rv = entry->CreateDescriptor(request,
2885 : accessGranted,
2886 0 : &descriptor);
2887 :
2888 : // post call to listener to report error or descriptor
2889 0 : rv = NotifyListener(request, descriptor, accessGranted, rv);
2890 0 : delete request;
2891 0 : if (NS_FAILED(rv)) {
2892 : // XXX what to do?
2893 : }
2894 :
2895 : } else {
2896 : // read-only request to an invalid entry - need to wait for
2897 : // the entry to become valid so we post an event to process
2898 : // the request again later (bug #467392)
2899 : nsCOMPtr<nsIRunnable> ev =
2900 0 : new nsProcessRequestEvent(request);
2901 0 : rv = DispatchToCacheIOThread(ev);
2902 0 : if (NS_FAILED(rv)) {
2903 0 : delete request; // avoid leak
2904 : }
2905 : }
2906 : } else {
2907 :
2908 : // Synchronous request
2909 0 : request->WakeUp();
2910 : }
2911 0 : if (newWriter) break; // process remaining requests after validation
2912 0 : request = nextRequest;
2913 : }
2914 :
2915 0 : return NS_OK;
2916 : }
2917 :
2918 : bool
2919 0 : nsCacheService::IsDoomListEmpty()
2920 : {
2921 0 : nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2922 0 : return &mDoomedEntries == entry;
2923 : }
2924 :
2925 : void
2926 0 : nsCacheService::ClearDoomList()
2927 : {
2928 0 : nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2929 :
2930 0 : while (entry != &mDoomedEntries) {
2931 0 : nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2932 :
2933 0 : entry->DetachDescriptors();
2934 0 : DeactivateEntry(entry);
2935 0 : entry = next;
2936 : }
2937 0 : }
2938 :
2939 : void
2940 0 : nsCacheService::DoomActiveEntries(DoomCheckFn check)
2941 : {
2942 0 : AutoTArray<nsCacheEntry*, 8> array;
2943 :
2944 0 : for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
2945 : nsCacheEntry* entry =
2946 0 : static_cast<nsCacheEntryHashTableEntry*>(iter.Get())->cacheEntry;
2947 :
2948 0 : if (check && !check(entry)) {
2949 0 : continue;
2950 : }
2951 :
2952 0 : array.AppendElement(entry);
2953 :
2954 : // entry is being removed from the active entry list
2955 0 : entry->MarkInactive();
2956 0 : iter.Remove();
2957 : }
2958 :
2959 0 : uint32_t count = array.Length();
2960 0 : for (uint32_t i = 0; i < count; ++i) {
2961 0 : DoomEntry_Internal(array[i], true);
2962 : }
2963 0 : }
2964 :
2965 : void
2966 0 : nsCacheService::CloseAllStreams()
2967 : {
2968 0 : nsTArray<RefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
2969 0 : nsTArray<RefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
2970 :
2971 : {
2972 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
2973 :
2974 0 : nsTArray<nsCacheEntry*> entries;
2975 :
2976 : #if DEBUG
2977 : // make sure there is no active entry
2978 0 : for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
2979 0 : auto entry = static_cast<nsCacheEntryHashTableEntry*>(iter.Get());
2980 0 : entries.AppendElement(entry->cacheEntry);
2981 : }
2982 0 : NS_ASSERTION(entries.IsEmpty(), "Bad state");
2983 : #endif
2984 :
2985 : // Get doomed entries
2986 0 : nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2987 0 : while (entry != &mDoomedEntries) {
2988 0 : nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2989 0 : entries.AppendElement(entry);
2990 0 : entry = next;
2991 : }
2992 :
2993 : // Iterate through all entries and collect input and output streams
2994 0 : for (size_t i = 0; i < entries.Length(); i++) {
2995 0 : entry = entries.ElementAt(i);
2996 :
2997 0 : nsTArray<RefPtr<nsCacheEntryDescriptor> > descs;
2998 0 : entry->GetDescriptors(descs);
2999 :
3000 0 : for (uint32_t j = 0 ; j < descs.Length() ; j++) {
3001 0 : if (descs[j]->mOutputWrapper)
3002 0 : outputs.AppendElement(descs[j]->mOutputWrapper);
3003 :
3004 0 : for (size_t k = 0; k < descs[j]->mInputWrappers.Length(); k++)
3005 0 : inputs.AppendElement(descs[j]->mInputWrappers[k]);
3006 : }
3007 : }
3008 : }
3009 :
3010 : uint32_t i;
3011 0 : for (i = 0 ; i < inputs.Length() ; i++)
3012 0 : inputs[i]->Close();
3013 :
3014 0 : for (i = 0 ; i < outputs.Length() ; i++)
3015 0 : outputs[i]->Close();
3016 0 : }
3017 :
3018 :
3019 : bool
3020 0 : nsCacheService::GetClearingEntries()
3021 : {
3022 0 : AssertOwnsLock();
3023 0 : return gService->mClearingEntries;
3024 : }
3025 :
3026 : // static
3027 1 : void nsCacheService::GetCacheBaseDirectoty(nsIFile ** result)
3028 : {
3029 1 : *result = nullptr;
3030 1 : if (!gService || !gService->mObserver)
3031 0 : return;
3032 :
3033 : nsCOMPtr<nsIFile> directory =
3034 2 : gService->mObserver->DiskCacheParentDirectory();
3035 1 : if (!directory)
3036 0 : return;
3037 :
3038 1 : directory->Clone(result);
3039 : }
3040 :
3041 : // static
3042 1 : void nsCacheService::GetDiskCacheDirectory(nsIFile ** result)
3043 : {
3044 2 : nsCOMPtr<nsIFile> directory;
3045 1 : GetCacheBaseDirectoty(getter_AddRefs(directory));
3046 1 : if (!directory)
3047 0 : return;
3048 :
3049 1 : nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
3050 1 : if (NS_FAILED(rv))
3051 0 : return;
3052 :
3053 1 : directory.forget(result);
3054 : }
3055 :
3056 : // static
3057 0 : void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
3058 : {
3059 0 : nsCOMPtr<nsIFile> directory;
3060 0 : GetCacheBaseDirectoty(getter_AddRefs(directory));
3061 0 : if (!directory)
3062 0 : return;
3063 :
3064 0 : nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
3065 0 : if (NS_FAILED(rv))
3066 0 : return;
3067 :
3068 0 : directory.forget(result);
3069 : }
3070 :
3071 :
3072 : void
3073 0 : nsCacheService::LogCacheStatistics()
3074 : {
3075 0 : uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
3076 0 : ((double)(mCacheHits + mCacheMisses))) * 100);
3077 0 : CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
3078 0 : CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
3079 0 : CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
3080 0 : CACHE_LOG_INFO((" Cache Misses = %d\n", mCacheMisses));
3081 0 : CACHE_LOG_INFO((" Cache Hit %% = %d%%\n", hitPercentage));
3082 0 : CACHE_LOG_INFO((" Max Key Length = %d\n", mMaxKeyLength));
3083 0 : CACHE_LOG_INFO((" Max Meta Size = %d\n", mMaxMetaSize));
3084 0 : CACHE_LOG_INFO((" Max Data Size = %d\n", mMaxDataSize));
3085 0 : CACHE_LOG_INFO(("\n"));
3086 0 : CACHE_LOG_INFO((" Deactivate Failures = %d\n",
3087 : mDeactivateFailures));
3088 0 : CACHE_LOG_INFO((" Deactivated Unbound Entries = %d\n",
3089 : mDeactivatedUnboundEntries));
3090 0 : }
3091 :
3092 : nsresult
3093 0 : nsCacheService::SetDiskSmartSize()
3094 : {
3095 0 : nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
3096 :
3097 0 : if (!gService) return NS_ERROR_NOT_AVAILABLE;
3098 :
3099 0 : return gService->SetDiskSmartSize_Locked();
3100 : }
3101 :
3102 : nsresult
3103 0 : nsCacheService::SetDiskSmartSize_Locked()
3104 : {
3105 : nsresult rv;
3106 :
3107 0 : if (mozilla::net::CacheObserver::UseNewCache()) {
3108 0 : return NS_ERROR_NOT_AVAILABLE;
3109 : }
3110 :
3111 0 : if (!mObserver->DiskCacheParentDirectory())
3112 0 : return NS_ERROR_NOT_AVAILABLE;
3113 :
3114 0 : if (!mDiskDevice)
3115 0 : return NS_ERROR_NOT_AVAILABLE;
3116 :
3117 0 : if (!mObserver->SmartSizeEnabled())
3118 0 : return NS_ERROR_NOT_AVAILABLE;
3119 :
3120 0 : nsAutoString cachePath;
3121 0 : rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
3122 0 : if (NS_SUCCEEDED(rv)) {
3123 : nsCOMPtr<nsIRunnable> event =
3124 0 : new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
3125 0 : mObserver->ShouldUseOldMaxSmartSize());
3126 0 : DispatchToCacheIOThread(event);
3127 : } else {
3128 0 : return NS_ERROR_FAILURE;
3129 : }
3130 :
3131 0 : return NS_OK;
3132 : }
3133 :
3134 : void
3135 2 : nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
3136 : nsIFile *aNewCacheDir,
3137 : const char *aCacheSubdir)
3138 : {
3139 : bool same;
3140 2 : if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
3141 4 : return;
3142 :
3143 0 : nsCOMPtr<nsIFile> aOldCacheSubdir;
3144 0 : aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
3145 :
3146 0 : nsresult rv = aOldCacheSubdir->AppendNative(
3147 0 : nsDependentCString(aCacheSubdir));
3148 0 : if (NS_FAILED(rv))
3149 0 : return;
3150 :
3151 : bool exists;
3152 0 : if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
3153 0 : return;
3154 :
3155 0 : nsCOMPtr<nsIFile> aNewCacheSubdir;
3156 0 : aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
3157 :
3158 0 : rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
3159 0 : if (NS_FAILED(rv))
3160 0 : return;
3161 :
3162 0 : nsAutoCString newPath;
3163 0 : rv = aNewCacheSubdir->GetNativePath(newPath);
3164 0 : if (NS_FAILED(rv))
3165 0 : return;
3166 :
3167 0 : if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
3168 : // New cache directory does not exist, try to move the old one here
3169 : // rename needs an empty target directory
3170 :
3171 : // Make sure the parent of the target sub-dir exists
3172 0 : rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
3173 0 : if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
3174 0 : nsAutoCString oldPath;
3175 0 : rv = aOldCacheSubdir->GetNativePath(oldPath);
3176 0 : if (NS_FAILED(rv))
3177 0 : return;
3178 0 : if (rename(oldPath.get(), newPath.get()) == 0)
3179 0 : return;
3180 : }
3181 : }
3182 :
3183 : // Delay delete by 1 minute to avoid IO thrash on startup.
3184 0 : nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
3185 : }
3186 :
3187 : static bool
3188 0 : IsEntryPrivate(nsCacheEntry* entry)
3189 : {
3190 0 : return entry->IsPrivate();
3191 : }
3192 :
3193 : void
3194 0 : nsCacheService::LeavePrivateBrowsing()
3195 : {
3196 0 : nsCacheServiceAutoLock lock;
3197 :
3198 0 : gService->DoomActiveEntries(IsEntryPrivate);
3199 :
3200 0 : if (gService->mMemoryDevice) {
3201 : // clear memory cache
3202 0 : gService->mMemoryDevice->EvictPrivateEntries();
3203 : }
3204 0 : }
3205 :
3206 0 : MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
3207 :
3208 : NS_IMETHODIMP
3209 0 : nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
3210 : nsISupports* aData, bool aAnonymize)
3211 : {
3212 0 : size_t disk = 0;
3213 0 : if (mDiskDevice) {
3214 : nsCacheServiceAutoLock
3215 0 : lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
3216 0 : disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
3217 : }
3218 :
3219 0 : size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
3220 :
3221 0 : MOZ_COLLECT_REPORT(
3222 : "explicit/network/disk-cache", KIND_HEAP, UNITS_BYTES, disk,
3223 0 : "Memory used by the network disk cache.");
3224 :
3225 0 : MOZ_COLLECT_REPORT(
3226 : "explicit/network/memory-cache", KIND_HEAP, UNITS_BYTES, memory,
3227 0 : "Memory used by the network memory cache.");
3228 :
3229 0 : return NS_OK;
3230 : }
|