summaryrefslogtreecommitdiffstats
path: root/netwerk/cache
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache')
-rw-r--r--netwerk/cache/moz.build51
-rw-r--r--netwerk/cache/nsApplicationCache.h29
-rw-r--r--netwerk/cache/nsApplicationCacheService.cpp268
-rw-r--r--netwerk/cache/nsApplicationCacheService.h28
-rw-r--r--netwerk/cache/nsCache.cpp95
-rw-r--r--netwerk/cache/nsCache.h42
-rw-r--r--netwerk/cache/nsCacheDevice.h63
-rw-r--r--netwerk/cache/nsCacheEntry.cpp508
-rw-r--r--netwerk/cache/nsCacheEntry.h302
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.cpp1475
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.h237
-rw-r--r--netwerk/cache/nsCacheMetaData.cpp162
-rw-r--r--netwerk/cache/nsCacheMetaData.h45
-rw-r--r--netwerk/cache/nsCacheRequest.h158
-rw-r--r--netwerk/cache/nsCacheService.cpp3209
-rw-r--r--netwerk/cache/nsCacheService.h392
-rw-r--r--netwerk/cache/nsCacheSession.cpp147
-rw-r--r--netwerk/cache/nsCacheSession.h67
-rw-r--r--netwerk/cache/nsCacheUtils.cpp89
-rw-r--r--netwerk/cache/nsCacheUtils.h43
-rw-r--r--netwerk/cache/nsDeleteDir.cpp454
-rw-r--r--netwerk/cache/nsDeleteDir.h80
-rw-r--r--netwerk/cache/nsDiskCache.h72
-rw-r--r--netwerk/cache/nsDiskCacheBinding.cpp371
-rw-r--r--netwerk/cache/nsDiskCacheBinding.h123
-rw-r--r--netwerk/cache/nsDiskCacheBlockFile.cpp404
-rw-r--r--netwerk/cache/nsDiskCacheBlockFile.h69
-rw-r--r--netwerk/cache/nsDiskCacheDevice.cpp1149
-rw-r--r--netwerk/cache/nsDiskCacheDevice.h116
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.cpp2906
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.h290
-rw-r--r--netwerk/cache/nsDiskCacheEntry.cpp133
-rw-r--r--netwerk/cache/nsDiskCacheEntry.h100
-rw-r--r--netwerk/cache/nsDiskCacheMap.cpp1430
-rw-r--r--netwerk/cache/nsDiskCacheMap.h577
-rw-r--r--netwerk/cache/nsDiskCacheStreams.cpp693
-rw-r--r--netwerk/cache/nsDiskCacheStreams.h70
-rw-r--r--netwerk/cache/nsICache.idl142
-rw-r--r--netwerk/cache/nsICacheEntryDescriptor.idl164
-rw-r--r--netwerk/cache/nsICacheListener.idl32
-rw-r--r--netwerk/cache/nsICacheService.idl98
-rw-r--r--netwerk/cache/nsICacheSession.idl88
-rw-r--r--netwerk/cache/nsICacheVisitor.idl123
-rw-r--r--netwerk/cache/nsMemoryCacheDevice.cpp617
-rw-r--r--netwerk/cache/nsMemoryCacheDevice.h124
45 files changed, 17835 insertions, 0 deletions
diff --git a/netwerk/cache/moz.build b/netwerk/cache/moz.build
new file mode 100644
index 000000000..adf6e8bd2
--- /dev/null
+++ b/netwerk/cache/moz.build
@@ -0,0 +1,51 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsICache.idl',
+ 'nsICacheEntryDescriptor.idl',
+ 'nsICacheListener.idl',
+ 'nsICacheService.idl',
+ 'nsICacheSession.idl',
+ 'nsICacheVisitor.idl',
+]
+
+XPIDL_MODULE = 'necko_cache'
+
+EXPORTS += [
+ 'nsApplicationCacheService.h',
+ 'nsCacheService.h',
+ 'nsDeleteDir.h'
+]
+
+UNIFIED_SOURCES += [
+ 'nsApplicationCacheService.cpp',
+ 'nsCache.cpp',
+ 'nsCacheEntry.cpp',
+ 'nsCacheEntryDescriptor.cpp',
+ 'nsCacheMetaData.cpp',
+ 'nsCacheService.cpp',
+ 'nsCacheSession.cpp',
+ 'nsCacheUtils.cpp',
+ 'nsDeleteDir.cpp',
+ 'nsDiskCacheBinding.cpp',
+ 'nsDiskCacheBlockFile.cpp',
+ 'nsDiskCacheDevice.cpp',
+ 'nsDiskCacheDeviceSQL.cpp',
+ 'nsDiskCacheEntry.cpp',
+ 'nsDiskCacheMap.cpp',
+ 'nsDiskCacheStreams.cpp',
+ 'nsMemoryCacheDevice.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/cache/nsApplicationCache.h b/netwerk/cache/nsApplicationCache.h
new file mode 100644
index 000000000..fc8501005
--- /dev/null
+++ b/netwerk/cache/nsApplicationCache.h
@@ -0,0 +1,29 @@
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+class nsApplicationCache : public nsIApplicationCache
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHE
+
+ nsApplicationCache(nsOfflineCacheDevice *device,
+ const nsACString &group,
+ const nsACString &clientID);
+
+ nsApplicationCache();
+
+ void MarkInvalid();
+
+private:
+ virtual ~nsApplicationCache();
+
+ RefPtr<nsOfflineCacheDevice> mDevice;
+ nsCString mGroup;
+ nsCString mClientID;
+ bool mValid;
+};
+
diff --git a/netwerk/cache/nsApplicationCacheService.cpp b/netwerk/cache/nsApplicationCacheService.cpp
new file mode 100644
index 000000000..17012518d
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDiskCache.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheService.h"
+#include "nsApplicationCacheService.h"
+#include "nsCRT.h"
+#include "mozIApplicationClearPrivateDataParams.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "mozilla/LoadContextInfo.h"
+
+using namespace mozilla;
+
+static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
+
+//-----------------------------------------------------------------------------
+// nsApplicationCacheService
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsApplicationCacheService, nsIApplicationCacheService)
+
+nsApplicationCacheService::nsApplicationCacheService()
+{
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
+ mCacheService = nsCacheService::GlobalInstance();
+}
+
+nsApplicationCacheService::~nsApplicationCacheService()
+{
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::BuildGroupIDForInfo(
+ nsIURI *aManifestURL,
+ nsILoadContextInfo *aLoadContextInfo,
+ nsACString &_result)
+{
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ if (aLoadContextInfo) {
+ aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
+ }
+
+ rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+ aManifestURL, originSuffix, _result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::BuildGroupIDForSuffix(
+ nsIURI *aManifestURL,
+ nsACString const &aOriginSuffix,
+ nsACString &_result)
+{
+ nsresult rv;
+
+ rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+ aManifestURL, aOriginSuffix, _result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CreateApplicationCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CreateApplicationCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CreateCustomApplicationCache(const nsACString & group,
+ nsIFile *profileDir,
+ int32_t quota,
+ nsIApplicationCache **out)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetCustomOfflineDevice(profileDir,
+ quota,
+ getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CreateApplicationCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetApplicationCache(const nsACString &clientID,
+ nsIApplicationCache **out)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetApplicationCache(clientID, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetActiveCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetActiveCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::DeactivateGroup(const nsACString &group)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->DeactivateGroup(group);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::ChooseApplicationCache(const nsACString &key,
+ nsILoadContextInfo *aLoadContextInfo,
+ nsIApplicationCache **out)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return device->ChooseApplicationCache(key, aLoadContextInfo, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString &key)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CacheOpportunistically(cache, key);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::Evict(nsILoadContextInfo *aInfo)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->Evict(aInfo);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::EvictMatchingOriginAttributes(nsAString const &aPattern)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ NS_ERROR("Could not parse OriginAttributesPattern JSON in clear-origin-attributes-data notification");
+ return NS_ERROR_FAILURE;
+ }
+
+ return device->Evict(pattern);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetGroups(uint32_t *count,
+ char ***keys)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetGroups(count, keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetGroupsTimeOrdered(uint32_t *count,
+ char ***keys)
+{
+ if (!mCacheService)
+ return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetGroupsTimeOrdered(count, keys);
+}
+
+//-----------------------------------------------------------------------------
+// AppCacheClearDataObserver: handles clearing appcache data for app uninstall
+// and clearing user data events.
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class AppCacheClearDataObserver final : public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, "clear-origin-attributes-data"));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> cacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return cacheService->EvictMatchingOriginAttributes(nsDependentString(aData));
+ }
+
+private:
+ ~AppCacheClearDataObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(AppCacheClearDataObserver, nsIObserver)
+
+} // namespace
+
+// Instantiates and registers AppCacheClearDataObserver for notifications
+void
+nsApplicationCacheService::AppClearDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ RefPtr<AppCacheClearDataObserver> obs = new AppCacheClearDataObserver();
+ observerService->AddObserver(obs, "clear-origin-attributes-data", /*ownsWeak=*/ false);
+ }
+}
diff --git a/netwerk/cache/nsApplicationCacheService.h b/netwerk/cache/nsApplicationCacheService.h
new file mode 100644
index 000000000..73bd32206
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsApplicationCacheService_h_
+#define _nsApplicationCacheService_h_
+
+#include "nsIApplicationCacheService.h"
+#include "mozilla/Attributes.h"
+
+class nsCacheService;
+
+class nsApplicationCacheService final : public nsIApplicationCacheService
+{
+public:
+ nsApplicationCacheService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHESERVICE
+
+ static void AppClearDataObserverInit();
+
+private:
+ ~nsApplicationCacheService();
+ RefPtr<nsCacheService> mCacheService;
+};
+
+#endif // _nsApplicationCacheService_h_
diff --git a/netwerk/cache/nsCache.cpp b/netwerk/cache/nsCache.cpp
new file mode 100644
index 000000000..7f5d6a071
--- /dev/null
+++ b/netwerk/cache/nsCache.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsReadableUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsString.h"
+
+
+/**
+ * Cache Service Utility Functions
+ */
+
+mozilla::LazyLogModule gCacheLog("cache");
+
+void
+CacheLogPrintPath(mozilla::LogLevel level, const char * format, nsIFile * item)
+{
+ nsAutoCString path;
+ nsresult rv = item->GetNativePath(path);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(gCacheLog, level, (format, path.get()));
+ } else {
+ MOZ_LOG(gCacheLog, level, ("GetNativePath failed: %x", rv));
+ }
+}
+
+
+uint32_t
+SecondsFromPRTime(PRTime prTime)
+{
+ int64_t microSecondsPerSecond = PR_USEC_PER_SEC;
+ return uint32_t(prTime / microSecondsPerSecond);
+}
+
+
+PRTime
+PRTimeFromSeconds(uint32_t seconds)
+{
+ int64_t intermediateResult = seconds;
+ PRTime prTime = intermediateResult * PR_USEC_PER_SEC;
+ return prTime;
+}
+
+
+nsresult
+ClientIDFromCacheKey(const nsACString& key, char ** result)
+{
+ nsresult rv = NS_OK;
+ *result = nullptr;
+
+ nsReadingIterator<char> colon;
+ key.BeginReading(colon);
+
+ nsReadingIterator<char> start;
+ key.BeginReading(start);
+
+ nsReadingIterator<char> end;
+ key.EndReading(end);
+
+ if (FindCharInReadable(':', colon, end)) {
+ *result = ToNewCString( Substring(start, colon));
+ if (!*result) rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+
+nsresult
+ClientKeyFromCacheKey(const nsCString& key, nsACString &result)
+{
+ nsresult rv = NS_OK;
+
+ nsReadingIterator<char> start;
+ key.BeginReading(start);
+
+ nsReadingIterator<char> end;
+ key.EndReading(end);
+
+ if (FindCharInReadable(':', start, end)) {
+ ++start; // advance past clientID ':' delimiter
+ result.Assign(Substring(start, end));
+ } else {
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ rv = NS_ERROR_UNEXPECTED;
+ result.Truncate(0);
+ }
+ return rv;
+}
diff --git a/netwerk/cache/nsCache.h b/netwerk/cache/nsCache.h
new file mode 100644
index 000000000..3c4513140
--- /dev/null
+++ b/netwerk/cache/nsCache.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Cache Service Utility Functions
+ */
+
+#ifndef _nsCache_h_
+#define _nsCache_h_
+
+#include "mozilla/Logging.h"
+#include "nsISupports.h"
+#include "nsIFile.h"
+#include "nsAString.h"
+#include "prtime.h"
+#include "nsError.h"
+
+// PR_LOG args = "format string", arg, arg, ...
+extern mozilla::LazyLogModule gCacheLog;
+void CacheLogPrintPath(mozilla::LogLevel level,
+ const char * format,
+ nsIFile * item);
+#define CACHE_LOG_INFO(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Info, args)
+#define CACHE_LOG_ERROR(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Error, args)
+#define CACHE_LOG_WARNING(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Warning, args)
+#define CACHE_LOG_DEBUG(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Debug, args)
+#define CACHE_LOG_PATH(level, format, item) \
+ CacheLogPrintPath(level, format, item)
+
+
+extern uint32_t SecondsFromPRTime(PRTime prTime);
+extern PRTime PRTimeFromSeconds(uint32_t seconds);
+
+
+extern nsresult ClientIDFromCacheKey(const nsACString& key, char ** result);
+extern nsresult ClientKeyFromCacheKey(const nsCString& key, nsACString &result);
+
+
+#endif // _nsCache_h
diff --git a/netwerk/cache/nsCacheDevice.h b/netwerk/cache/nsCacheDevice.h
new file mode 100644
index 000000000..967c5a034
--- /dev/null
+++ b/netwerk/cache/nsCacheDevice.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheDevice_h_
+#define _nsCacheDevice_h_
+
+#include "nspr.h"
+#include "nsError.h"
+#include "nsICache.h"
+
+class nsIFile;
+class nsCString;
+class nsCacheEntry;
+class nsICacheVisitor;
+class nsIInputStream;
+class nsIOutputStream;
+
+/******************************************************************************
+* nsCacheDevice
+*******************************************************************************/
+class nsCacheDevice {
+public:
+ nsCacheDevice() { MOZ_COUNT_CTOR(nsCacheDevice); }
+ virtual ~nsCacheDevice() { MOZ_COUNT_DTOR(nsCacheDevice); }
+
+ virtual nsresult Init() = 0;
+ virtual nsresult Shutdown() = 0;
+
+ virtual const char * GetDeviceID(void) = 0;
+ virtual nsCacheEntry * FindEntry( nsCString * key, bool *collision ) = 0;
+
+ virtual nsresult DeactivateEntry( nsCacheEntry * entry ) = 0;
+ virtual nsresult BindEntry( nsCacheEntry * entry ) = 0;
+ virtual void DoomEntry( nsCacheEntry * entry ) = 0;
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result) = 0;
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result) = 0;
+
+ virtual nsresult GetFileForEntry( nsCacheEntry * entry,
+ nsIFile ** result ) = 0;
+
+ virtual nsresult OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize ) = 0;
+
+ virtual nsresult Visit(nsICacheVisitor * visitor) = 0;
+
+ /**
+ * Device must evict entries associated with clientID. If clientID == nullptr, all
+ * entries must be evicted. Active entries must be doomed, rather than evicted.
+ */
+ virtual nsresult EvictEntries(const char * clientID) = 0;
+};
+
+#endif // _nsCacheDevice_h_
diff --git a/netwerk/cache/nsCacheEntry.cpp b/netwerk/cache/nsCacheEntry.cpp
new file mode 100644
index 000000000..6703394c7
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -0,0 +1,508 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCache.h"
+#include "nspr.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheMetaData.h"
+#include "nsCacheRequest.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "nsICacheService.h"
+#include "nsCacheService.h"
+#include "nsCacheDevice.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+nsCacheEntry::nsCacheEntry(const nsACString & key,
+ bool streamBased,
+ nsCacheStoragePolicy storagePolicy)
+ : mKey(key),
+ mFetchCount(0),
+ mLastFetched(0),
+ mLastModified(0),
+ mExpirationTime(nsICache::NO_EXPIRATION_TIME),
+ mFlags(0),
+ mPredictedDataSize(-1),
+ mDataSize(0),
+ mCacheDevice(nullptr),
+ mCustomDevice(nullptr),
+ mData(nullptr)
+{
+ MOZ_COUNT_CTOR(nsCacheEntry);
+ PR_INIT_CLIST(this);
+ PR_INIT_CLIST(&mRequestQ);
+ PR_INIT_CLIST(&mDescriptorQ);
+
+ if (streamBased) MarkStreamBased();
+ SetStoragePolicy(storagePolicy);
+
+ MarkPublic();
+}
+
+
+nsCacheEntry::~nsCacheEntry()
+{
+ MOZ_COUNT_DTOR(nsCacheEntry);
+
+ if (mData)
+ nsCacheService::ReleaseObject_Locked(mData, mThread);
+}
+
+
+nsresult
+nsCacheEntry::Create( const char * key,
+ bool streamBased,
+ nsCacheStoragePolicy storagePolicy,
+ nsCacheDevice * device,
+ nsCacheEntry ** result)
+{
+ nsCacheEntry* entry = new nsCacheEntry(nsCString(key),
+ streamBased,
+ storagePolicy);
+ entry->SetCacheDevice(device);
+ *result = entry;
+ return NS_OK;
+}
+
+
+void
+nsCacheEntry::Fetched()
+{
+ mLastFetched = SecondsFromPRTime(PR_Now());
+ ++mFetchCount;
+ MarkEntryDirty();
+}
+
+
+const char *
+nsCacheEntry::GetDeviceID()
+{
+ if (mCacheDevice) return mCacheDevice->GetDeviceID();
+ return nullptr;
+}
+
+
+void
+nsCacheEntry::TouchData()
+{
+ mLastModified = SecondsFromPRTime(PR_Now());
+ MarkDataDirty();
+}
+
+
+void
+nsCacheEntry::SetData(nsISupports * data)
+{
+ if (mData) {
+ nsCacheService::ReleaseObject_Locked(mData, mThread);
+ mData = nullptr;
+ }
+
+ if (data) {
+ NS_ADDREF(mData = data);
+ mThread = do_GetCurrentThread();
+ }
+}
+
+
+void
+nsCacheEntry::TouchMetaData()
+{
+ mLastModified = SecondsFromPRTime(PR_Now());
+ MarkMetaDataDirty();
+}
+
+
+/**
+ * cache entry states
+ * 0 descriptors (new entry)
+ * 0 descriptors (existing, bound entry)
+ * n descriptors (existing, bound entry) valid
+ * n descriptors (existing, bound entry) not valid (wait until valid or doomed)
+ */
+
+nsresult
+nsCacheEntry::RequestAccess(nsCacheRequest * request, nsCacheAccessMode *accessGranted)
+{
+ nsresult rv = NS_OK;
+
+ if (IsDoomed()) return NS_ERROR_CACHE_ENTRY_DOOMED;
+
+ if (!IsInitialized()) {
+ // brand new, unbound entry
+ if (request->IsStreamBased()) MarkStreamBased();
+ MarkInitialized();
+
+ *accessGranted = request->AccessRequested() & nsICache::ACCESS_WRITE;
+ NS_ASSERTION(*accessGranted, "new cache entry for READ-ONLY request");
+ PR_APPEND_LINK(request, &mRequestQ);
+ return rv;
+ }
+
+ if (IsStreamData() != request->IsStreamBased()) {
+ *accessGranted = nsICache::ACCESS_NONE;
+ return request->IsStreamBased() ?
+ NS_ERROR_CACHE_DATA_IS_NOT_STREAM : NS_ERROR_CACHE_DATA_IS_STREAM;
+ }
+
+ if (PR_CLIST_IS_EMPTY(&mDescriptorQ)) {
+ // 1st descriptor for existing bound entry
+ *accessGranted = request->AccessRequested();
+ if (*accessGranted & nsICache::ACCESS_WRITE) {
+ MarkInvalid();
+ } else {
+ MarkValid();
+ }
+ } else {
+ // nth request for existing, bound entry
+ *accessGranted = request->AccessRequested() & ~nsICache::ACCESS_WRITE;
+ if (!IsValid())
+ rv = NS_ERROR_CACHE_WAIT_FOR_VALIDATION;
+ }
+ PR_APPEND_LINK(request,&mRequestQ);
+
+ return rv;
+}
+
+
+nsresult
+nsCacheEntry::CreateDescriptor(nsCacheRequest * request,
+ nsCacheAccessMode accessGranted,
+ nsICacheEntryDescriptor ** result)
+{
+ NS_ENSURE_ARG_POINTER(request && result);
+
+ nsCacheEntryDescriptor * descriptor =
+ new nsCacheEntryDescriptor(this, accessGranted);
+
+ // XXX check request is on q
+ PR_REMOVE_AND_INIT_LINK(request); // remove request regardless of success
+
+ if (descriptor == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PR_APPEND_LINK(descriptor, &mDescriptorQ);
+
+ CACHE_LOG_DEBUG((" descriptor %p created for request %p on entry %p\n",
+ descriptor, request, this));
+
+ NS_ADDREF(*result = descriptor);
+ return NS_OK;
+}
+
+
+bool
+nsCacheEntry::RemoveRequest(nsCacheRequest * request)
+{
+ // XXX if debug: verify this request belongs to this entry
+ PR_REMOVE_AND_INIT_LINK(request);
+
+ // return true if this entry should stay active
+ return !((PR_CLIST_IS_EMPTY(&mRequestQ)) &&
+ (PR_CLIST_IS_EMPTY(&mDescriptorQ)));
+}
+
+
+bool
+nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor * descriptor,
+ bool * doomEntry)
+{
+ NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!");
+
+ *doomEntry = descriptor->ClearCacheEntry();
+
+ PR_REMOVE_AND_INIT_LINK(descriptor);
+
+ if (!PR_CLIST_IS_EMPTY(&mDescriptorQ))
+ return true; // stay active if we still have open descriptors
+
+ if (PR_CLIST_IS_EMPTY(&mRequestQ))
+ return false; // no descriptors or requests, we can deactivate
+
+ return true; // find next best request to give a descriptor to
+}
+
+
+void
+nsCacheEntry::DetachDescriptors()
+{
+ nsCacheEntryDescriptor * descriptor =
+ (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ);
+
+ while (descriptor != &mDescriptorQ) {
+ nsCacheEntryDescriptor * nextDescriptor =
+ (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor);
+
+ descriptor->ClearCacheEntry();
+ PR_REMOVE_AND_INIT_LINK(descriptor);
+ descriptor = nextDescriptor;
+ }
+}
+
+
+void
+nsCacheEntry::GetDescriptors(
+ nsTArray<RefPtr<nsCacheEntryDescriptor> > &outDescriptors)
+{
+ nsCacheEntryDescriptor * descriptor =
+ (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ);
+
+ while (descriptor != &mDescriptorQ) {
+ nsCacheEntryDescriptor * nextDescriptor =
+ (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor);
+
+ outDescriptors.AppendElement(descriptor);
+ descriptor = nextDescriptor;
+ }
+}
+
+
+/******************************************************************************
+ * nsCacheEntryInfo - for implementing about:cache
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsCacheEntryInfo, nsICacheEntryInfo)
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetClientID(char ** clientID)
+{
+ NS_ENSURE_ARG_POINTER(clientID);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientIDFromCacheKey(*mCacheEntry->Key(), clientID);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ NS_ENSURE_ARG_POINTER(deviceID);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *deviceID = NS_strdup(mCacheEntry->GetDeviceID());
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetKey(nsACString &key)
+{
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientKeyFromCacheKey(*mCacheEntry->Key(), key);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetFetchCount(int32_t * fetchCount)
+{
+ NS_ENSURE_ARG_POINTER(fetchCount);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *fetchCount = mCacheEntry->FetchCount();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetLastFetched(uint32_t * lastFetched)
+{
+ NS_ENSURE_ARG_POINTER(lastFetched);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *lastFetched = mCacheEntry->LastFetched();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetLastModified(uint32_t * lastModified)
+{
+ NS_ENSURE_ARG_POINTER(lastModified);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *lastModified = mCacheEntry->LastModified();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetExpirationTime(uint32_t * expirationTime)
+{
+ NS_ENSURE_ARG_POINTER(expirationTime);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *expirationTime = mCacheEntry->ExpirationTime();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetDataSize(uint32_t * dataSize)
+{
+ NS_ENSURE_ARG_POINTER(dataSize);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *dataSize = mCacheEntry->DataSize();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryInfo::IsStreamBased(bool * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->IsStreamData();
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsCacheEntryHashTable
+ *****************************************************************************/
+
+const PLDHashTableOps
+nsCacheEntryHashTable::ops =
+{
+ HashKey,
+ MatchEntry,
+ MoveEntry,
+ ClearEntry
+};
+
+
+nsCacheEntryHashTable::nsCacheEntryHashTable()
+ : table(&ops, sizeof(nsCacheEntryHashTableEntry), kInitialTableLength)
+ , initialized(false)
+{
+ MOZ_COUNT_CTOR(nsCacheEntryHashTable);
+}
+
+
+nsCacheEntryHashTable::~nsCacheEntryHashTable()
+{
+ MOZ_COUNT_DTOR(nsCacheEntryHashTable);
+ if (initialized)
+ Shutdown();
+}
+
+
+void
+nsCacheEntryHashTable::Init()
+{
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = true;
+}
+
+void
+nsCacheEntryHashTable::Shutdown()
+{
+ if (initialized) {
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = false;
+ }
+}
+
+
+nsCacheEntry *
+nsCacheEntryHashTable::GetEntry( const nsCString * key)
+{
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ if (!initialized) return nullptr;
+
+ PLDHashEntryHdr *hashEntry = table.Search(key);
+ return hashEntry ? ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry
+ : nullptr;
+}
+
+
+nsresult
+nsCacheEntryHashTable::AddEntry( nsCacheEntry *cacheEntry)
+{
+ PLDHashEntryHdr *hashEntry;
+
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ if (!initialized) return NS_ERROR_NOT_INITIALIZED;
+ if (!cacheEntry) return NS_ERROR_NULL_POINTER;
+
+ hashEntry = table.Add(&(cacheEntry->mKey), fallible);
+
+ if (!hashEntry)
+ return NS_ERROR_FAILURE;
+ NS_ASSERTION(((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry == 0,
+ "### nsCacheEntryHashTable::AddEntry - entry already used");
+ ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = cacheEntry;
+
+ return NS_OK;
+}
+
+
+void
+nsCacheEntryHashTable::RemoveEntry( nsCacheEntry *cacheEntry)
+{
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ NS_ASSERTION(cacheEntry, "### cacheEntry == nullptr");
+
+ if (!initialized) return; // NS_ERROR_NOT_INITIALIZED
+
+#if DEBUG
+ // XXX debug code to make sure we have the entry we're trying to remove
+ nsCacheEntry *check = GetEntry(&(cacheEntry->mKey));
+ NS_ASSERTION(check == cacheEntry, "### Attempting to remove unknown cache entry!!!");
+#endif
+ table.Remove(&(cacheEntry->mKey));
+}
+
+PLDHashTable::Iterator
+nsCacheEntryHashTable::Iter()
+{
+ return PLDHashTable::Iterator(&table);
+}
+
+/**
+ * hash table operation callback functions
+ */
+
+PLDHashNumber
+nsCacheEntryHashTable::HashKey(const void *key)
+{
+ return HashString(*static_cast<const nsCString *>(key));
+}
+
+bool
+nsCacheEntryHashTable::MatchEntry(const PLDHashEntryHdr * hashEntry,
+ const void * key)
+{
+ NS_ASSERTION(key != nullptr, "### nsCacheEntryHashTable::MatchEntry : null key");
+ nsCacheEntry *cacheEntry = ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry;
+
+ return cacheEntry->mKey.Equals(*(nsCString *)key);
+}
+
+
+void
+nsCacheEntryHashTable::MoveEntry(PLDHashTable * /* table */,
+ const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to)
+{
+ ((nsCacheEntryHashTableEntry *)to)->cacheEntry =
+ ((nsCacheEntryHashTableEntry *)from)->cacheEntry;
+}
+
+
+void
+nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */,
+ PLDHashEntryHdr * hashEntry)
+{
+ ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = 0;
+}
diff --git a/netwerk/cache/nsCacheEntry.h b/netwerk/cache/nsCacheEntry.h
new file mode 100644
index 000000000..bef7d9ace
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.h
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheEntry_h_
+#define _nsCacheEntry_h_
+
+#include "nsICache.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsIThread.h"
+#include "nsCacheMetaData.h"
+
+#include "nspr.h"
+#include "PLDHashTable.h"
+#include "nsAutoPtr.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAString.h"
+
+class nsCacheDevice;
+class nsCacheMetaData;
+class nsCacheRequest;
+class nsCacheEntryDescriptor;
+
+/******************************************************************************
+* nsCacheEntry
+*******************************************************************************/
+class nsCacheEntry : public PRCList
+{
+public:
+
+ nsCacheEntry(const nsACString & key,
+ bool streamBased,
+ nsCacheStoragePolicy storagePolicy);
+ ~nsCacheEntry();
+
+
+ static nsresult Create( const char * key,
+ bool streamBased,
+ nsCacheStoragePolicy storagePolicy,
+ nsCacheDevice * device,
+ nsCacheEntry ** result);
+
+ nsCString * Key() { return &mKey; }
+
+ int32_t FetchCount() { return mFetchCount; }
+ void SetFetchCount( int32_t count) { mFetchCount = count; }
+ void Fetched();
+
+ uint32_t LastFetched() { return mLastFetched; }
+ void SetLastFetched( uint32_t lastFetched) { mLastFetched = lastFetched; }
+
+ uint32_t LastModified() { return mLastModified; }
+ void SetLastModified( uint32_t lastModified) { mLastModified = lastModified; }
+
+ uint32_t ExpirationTime() { return mExpirationTime; }
+ void SetExpirationTime( uint32_t expires) { mExpirationTime = expires; }
+
+ uint32_t Size()
+ { return mDataSize + mMetaData.Size() + mKey.Length() ; }
+
+ nsCacheDevice * CacheDevice() { return mCacheDevice; }
+ void SetCacheDevice( nsCacheDevice * device) { mCacheDevice = device; }
+ void SetCustomCacheDevice( nsCacheDevice * device )
+ { mCustomDevice = device; }
+ nsCacheDevice * CustomCacheDevice() { return mCustomDevice; }
+ const char * GetDeviceID();
+
+ /**
+ * Data accessors
+ */
+ nsISupports *Data() { return mData; }
+ void SetData( nsISupports * data);
+
+ int64_t PredictedDataSize() { return mPredictedDataSize; }
+ void SetPredictedDataSize(int64_t size) { mPredictedDataSize = size; }
+
+ uint32_t DataSize() { return mDataSize; }
+ void SetDataSize( uint32_t size) { mDataSize = size; }
+
+ void TouchData();
+
+ /**
+ * Meta data accessors
+ */
+ const char * GetMetaDataElement( const char * key) { return mMetaData.GetElement(key); }
+ nsresult SetMetaDataElement( const char * key,
+ const char * value) { return mMetaData.SetElement(key, value); }
+ nsresult VisitMetaDataElements( nsICacheMetaDataVisitor * visitor) { return mMetaData.VisitElements(visitor); }
+ nsresult FlattenMetaData(char * buffer, uint32_t bufSize) { return mMetaData.FlattenMetaData(buffer, bufSize); }
+ nsresult UnflattenMetaData(const char * buffer, uint32_t bufSize) { return mMetaData.UnflattenMetaData(buffer, bufSize); }
+ uint32_t MetaDataSize() { return mMetaData.Size(); }
+
+ void TouchMetaData();
+
+
+ /**
+ * Security Info accessors
+ */
+ nsISupports* SecurityInfo() { return mSecurityInfo; }
+ void SetSecurityInfo( nsISupports * info) { mSecurityInfo = info; }
+
+
+ // XXX enumerate MetaData method
+
+
+ enum CacheEntryFlags {
+ eStoragePolicyMask = 0x000000FF,
+ eDoomedMask = 0x00000100,
+ eEntryDirtyMask = 0x00000200,
+ eDataDirtyMask = 0x00000400,
+ eMetaDataDirtyMask = 0x00000800,
+ eStreamDataMask = 0x00001000,
+ eActiveMask = 0x00002000,
+ eInitializedMask = 0x00004000,
+ eValidMask = 0x00008000,
+ eBindingMask = 0x00010000,
+ ePrivateMask = 0x00020000
+ };
+
+ void MarkBinding() { mFlags |= eBindingMask; }
+ void ClearBinding() { mFlags &= ~eBindingMask; }
+ bool IsBinding() { return (mFlags & eBindingMask) != 0; }
+
+ void MarkEntryDirty() { mFlags |= eEntryDirtyMask; }
+ void MarkEntryClean() { mFlags &= ~eEntryDirtyMask; }
+ void MarkDataDirty() { mFlags |= eDataDirtyMask; }
+ void MarkDataClean() { mFlags &= ~eDataDirtyMask; }
+ void MarkMetaDataDirty() { mFlags |= eMetaDataDirtyMask; }
+ void MarkMetaDataClean() { mFlags &= ~eMetaDataDirtyMask; }
+ void MarkStreamData() { mFlags |= eStreamDataMask; }
+ void MarkValid() { mFlags |= eValidMask; }
+ void MarkInvalid() { mFlags &= ~eValidMask; }
+ void MarkPrivate() { mFlags |= ePrivateMask; }
+ void MarkPublic() { mFlags &= ~ePrivateMask; }
+ // void MarkAllowedInMemory() { mFlags |= eAllowedInMemoryMask; }
+ // void MarkAllowedOnDisk() { mFlags |= eAllowedOnDiskMask; }
+
+ bool IsDoomed() { return (mFlags & eDoomedMask) != 0; }
+ bool IsEntryDirty() { return (mFlags & eEntryDirtyMask) != 0; }
+ bool IsDataDirty() { return (mFlags & eDataDirtyMask) != 0; }
+ bool IsMetaDataDirty() { return (mFlags & eMetaDataDirtyMask) != 0; }
+ bool IsStreamData() { return (mFlags & eStreamDataMask) != 0; }
+ bool IsActive() { return (mFlags & eActiveMask) != 0; }
+ bool IsInitialized() { return (mFlags & eInitializedMask) != 0; }
+ bool IsValid() { return (mFlags & eValidMask) != 0; }
+ bool IsInvalid() { return (mFlags & eValidMask) == 0; }
+ bool IsInUse() { return IsBinding() ||
+ !(PR_CLIST_IS_EMPTY(&mRequestQ) &&
+ PR_CLIST_IS_EMPTY(&mDescriptorQ)); }
+ bool IsNotInUse() { return !IsInUse(); }
+ bool IsPrivate() { return (mFlags & ePrivateMask) != 0; }
+
+
+ bool IsAllowedInMemory()
+ {
+ return (StoragePolicy() == nsICache::STORE_ANYWHERE) ||
+ (StoragePolicy() == nsICache::STORE_IN_MEMORY);
+ }
+
+ bool IsAllowedOnDisk()
+ {
+ return !IsPrivate() && ((StoragePolicy() == nsICache::STORE_ANYWHERE) ||
+ (StoragePolicy() == nsICache::STORE_ON_DISK));
+ }
+
+ bool IsAllowedOffline()
+ {
+ return (StoragePolicy() == nsICache::STORE_OFFLINE);
+ }
+
+ nsCacheStoragePolicy StoragePolicy()
+ {
+ return (nsCacheStoragePolicy)(mFlags & eStoragePolicyMask);
+ }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy)
+ {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mFlags &= ~eStoragePolicyMask; // clear storage policy bits
+ mFlags |= policy;
+ }
+
+
+ // methods for nsCacheService
+ nsresult RequestAccess( nsCacheRequest * request, nsCacheAccessMode *accessGranted);
+ nsresult CreateDescriptor( nsCacheRequest * request,
+ nsCacheAccessMode accessGranted,
+ nsICacheEntryDescriptor ** result);
+
+ bool RemoveRequest( nsCacheRequest * request);
+ bool RemoveDescriptor( nsCacheEntryDescriptor * descriptor,
+ bool * doomEntry);
+
+ void GetDescriptors(nsTArray<RefPtr<nsCacheEntryDescriptor> > &outDescriptors);
+
+private:
+ friend class nsCacheEntryHashTable;
+ friend class nsCacheService;
+
+ void DetachDescriptors();
+
+ // internal methods
+ void MarkDoomed() { mFlags |= eDoomedMask; }
+ void MarkStreamBased() { mFlags |= eStreamDataMask; }
+ void MarkInitialized() { mFlags |= eInitializedMask; }
+ void MarkActive() { mFlags |= eActiveMask; }
+ void MarkInactive() { mFlags &= ~eActiveMask; }
+
+ nsCString mKey;
+ uint32_t mFetchCount; // 4
+ uint32_t mLastFetched; // 4
+ uint32_t mLastModified; // 4
+ uint32_t mLastValidated; // 4
+ uint32_t mExpirationTime; // 4
+ uint32_t mFlags; // 4
+ int64_t mPredictedDataSize; // Size given by ContentLength.
+ uint32_t mDataSize; // 4
+ nsCacheDevice * mCacheDevice; // 4
+ nsCacheDevice * mCustomDevice; // 4
+ nsCOMPtr<nsISupports> mSecurityInfo; //
+ nsISupports * mData; // strong ref
+ nsCOMPtr<nsIThread> mThread;
+ nsCacheMetaData mMetaData; // 4
+ PRCList mRequestQ; // 8
+ PRCList mDescriptorQ; // 8
+};
+
+
+/******************************************************************************
+* nsCacheEntryInfo
+*******************************************************************************/
+class nsCacheEntryInfo : public nsICacheEntryInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ explicit nsCacheEntryInfo(nsCacheEntry* entry)
+ : mCacheEntry(entry)
+ {
+ }
+
+ void DetachEntry() { mCacheEntry = nullptr; }
+
+private:
+ nsCacheEntry * mCacheEntry;
+
+ virtual ~nsCacheEntryInfo() {}
+};
+
+
+/******************************************************************************
+* nsCacheEntryHashTable
+*******************************************************************************/
+
+struct nsCacheEntryHashTableEntry : public PLDHashEntryHdr
+{
+ nsCacheEntry *cacheEntry;
+};
+
+class nsCacheEntryHashTable
+{
+public:
+ nsCacheEntryHashTable();
+ ~nsCacheEntryHashTable();
+
+ void Init();
+ void Shutdown();
+
+ nsCacheEntry *GetEntry( const nsCString * key);
+ nsresult AddEntry( nsCacheEntry *entry);
+ void RemoveEntry( nsCacheEntry *entry);
+
+ PLDHashTable::Iterator Iter();
+
+private:
+ // PLDHashTable operation callbacks
+ static PLDHashNumber HashKey(const void *key);
+
+ static bool MatchEntry(const PLDHashEntryHdr * entry,
+ const void * key);
+
+ static void MoveEntry( PLDHashTable *table,
+ const PLDHashEntryHdr *from,
+ PLDHashEntryHdr *to);
+
+ static void ClearEntry( PLDHashTable *table, PLDHashEntryHdr *entry);
+
+ static void Finalize( PLDHashTable *table);
+
+ // member variables
+ static const PLDHashTableOps ops;
+ PLDHashTable table;
+ bool initialized;
+
+ static const uint32_t kInitialTableLength = 256;
+};
+
+#endif // _nsCacheEntry_h_
diff --git a/netwerk/cache/nsCacheEntryDescriptor.cpp b/netwerk/cache/nsCacheEntryDescriptor.cpp
new file mode 100644
index 000000000..b53ee3058
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -0,0 +1,1475 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICache.h"
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheEntry.h"
+#include "nsReadableUtils.h"
+#include "nsIOutputStream.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+#define kMinDecompressReadBufLen 1024
+#define kMinCompressWriteBufLen 1024
+
+
+/******************************************************************************
+ * nsAsyncDoomEvent
+ *****************************************************************************/
+
+class nsAsyncDoomEvent : public mozilla::Runnable {
+public:
+ nsAsyncDoomEvent(nsCacheEntryDescriptor *descriptor,
+ nsICacheListener *listener)
+ {
+ mDescriptor = descriptor;
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsresult status = NS_OK;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSASYNCDOOMEVENT_RUN));
+
+ if (mDescriptor->mCacheEntry) {
+ status = nsCacheService::gService->DoomEntry_Internal(
+ mDescriptor->mCacheEntry, true);
+ } else if (!mDescriptor->mDoomedOnClose) {
+ status = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (mListener) {
+ mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsCacheEntryDescriptor> mDescriptor;
+ nsICacheListener *mListener;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+
+NS_IMPL_ISUPPORTS(nsCacheEntryDescriptor,
+ nsICacheEntryDescriptor,
+ nsICacheEntryInfo)
+
+nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry,
+ nsCacheAccessMode accessGranted)
+ : mCacheEntry(entry),
+ mAccessGranted(accessGranted),
+ mOutputWrapper(nullptr),
+ mLock("nsCacheEntryDescriptor.mLock"),
+ mAsyncDoomPending(false),
+ mDoomedOnClose(false),
+ mClosingDescriptor(false)
+{
+ PR_INIT_CLIST(this);
+ NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor
+}
+
+
+nsCacheEntryDescriptor::~nsCacheEntryDescriptor()
+{
+ // No need to close if the cache entry has already been severed. This
+ // helps avoid a shutdown assertion (bug 285519) that is caused when
+ // consumers end up holding onto these objects past xpcom-shutdown. It's
+ // okay for them to do that because the cache service calls our Close
+ // method during xpcom-shutdown, so we don't need to complain about it.
+ if (mCacheEntry)
+ Close();
+
+ NS_ASSERTION(mInputWrappers.IsEmpty(),
+ "We have still some input wrapper!");
+ NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!");
+
+ nsCacheService * service = nsCacheService::GlobalInstance();
+ NS_RELEASE(service);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetClientID(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientIDFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID)
+{
+ NS_ENSURE_ARG_POINTER(aDeviceID);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* deviceID = mCacheEntry->GetDeviceID();
+ if (!deviceID) {
+ *aDeviceID = nullptr;
+ return NS_OK;
+ }
+
+ *aDeviceID = NS_strdup(deviceID);
+ return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetKey(nsACString &result)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETKEY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFetchCount(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->FetchCount();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastFetched(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastFetched();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastModified(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTMODIFIED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastModified();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetExpirationTime(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->ExpirationTime();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetExpirationTime(uint32_t expirationTime)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetExpirationTime(expirationTime);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_ISSTREAMBASED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->IsStreamData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(int64_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->PredictedDataSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(int64_t
+ predictedSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetPredictedDataSize(predictedSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (!val) {
+ *result = mCacheEntry->DataSize();
+ } else {
+ *result = atol(val);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetStorageDataSize(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->DataSize();
+
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheEntryDescriptor::RequestDataSizeChange(int32_t deltaSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_REQUESTDATASIZECHANGE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetDataSize(uint32_t dataSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX review for signed/unsigned math errors
+ int32_t deltaSize = dataSize - mCacheEntry->DataSize();
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ // this had better be NS_OK, this call instance is advisory for memory cache objects
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ } else {
+ NS_WARNING("failed SetDataSize() on memory cache object!");
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenInputStream(uint32_t offset, nsIInputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsInputStreamWrapper* cacheInput = nullptr;
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_READ))
+ return NS_ERROR_CACHE_READ_ACCESS_DENIED;
+
+ const char *val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (val) {
+ cacheInput = new nsDecompressInputStreamWrapper(this, offset);
+ } else {
+ cacheInput = new nsInputStreamWrapper(this, offset);
+ }
+ if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mInputWrappers.AppendElement(cacheInput);
+ }
+
+ NS_ADDREF(*result = cacheInput);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset, nsIOutputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsOutputStreamWrapper* cacheOutput = nullptr;
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE))
+ return NS_ERROR_CACHE_WRITE_ACCESS_DENIED;
+
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+ const char *val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if ((compressionLevel > 0) && val) {
+ cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
+ } else {
+ // clear compression flag when compression disabled - see bug 715198
+ if (val) {
+ mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
+ }
+ cacheOutput = new nsOutputStreamWrapper(this, offset);
+ }
+ if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mOutputWrapper = cacheOutput;
+ }
+
+ NS_ADDREF(*result = cacheOutput);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ NS_IF_ADDREF(*result = mCacheEntry->Data());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetCacheElement(nsISupports * cacheElement)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ return nsCacheService::SetCacheElement(mCacheEntry, cacheElement);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = mAccessGranted;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->StoragePolicy();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ // XXX validate policy against session?
+
+ bool storageEnabled = false;
+ storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy);
+ if (!storageEnabled) return NS_ERROR_FAILURE;
+
+ // Don't change the storage policy of entries we can't write
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Don't allow a cache entry to move from memory-only to anything else
+ if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY &&
+ policy != nsICache::STORE_IN_MEMORY)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetStoragePolicy(policy);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFile(nsIFile ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFILE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::GetFileForEntry(mCacheEntry, result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetSecurityInfo(nsISupports ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->SecurityInfo();
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetSecurityInfo(nsISupports * securityInfo)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetSecurityInfo(securityInfo);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Doom()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::DoomEntry(mCacheEntry);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOMANDFAILPENDINGREQUESTS));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::AsyncDoom(nsICacheListener *listener)
+{
+ bool asyncDoomPending;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ asyncDoomPending = mAsyncDoomPending;
+ mAsyncDoomPending = true;
+ }
+
+ if (asyncDoomPending) {
+ // AsyncDoom was already called. Notify listener if it is non-null,
+ // otherwise just return success.
+ if (listener) {
+ nsresult rv = NS_DispatchToCurrentThread(
+ new nsNotifyDoomListener(listener, NS_ERROR_NOT_AVAILABLE));
+ if (NS_SUCCEEDED(rv))
+ NS_IF_ADDREF(listener);
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new nsAsyncDoomEvent(this, listener);
+ return nsCacheService::DispatchToCacheIOThread(event);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::MarkValid()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_MARKVALID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = nsCacheService::ValidateEntry(mCacheEntry);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Close()
+{
+ RefPtr<nsOutputStreamWrapper> outputWrapper;
+ nsTArray<RefPtr<nsInputStreamWrapper> > inputWrappers;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // Make sure no other stream can be opened
+ mClosingDescriptor = true;
+ outputWrapper = mOutputWrapper;
+ for (size_t i = 0; i < mInputWrappers.Length(); i++)
+ inputWrappers.AppendElement(mInputWrappers[i]);
+ }
+
+ // Call Close() on the streams outside the lock since it might need to call
+ // methods that grab the cache service lock, e.g. compressed output stream
+ // when it finalizes the entry
+ if (outputWrapper) {
+ if (NS_FAILED(outputWrapper->Close())) {
+ NS_WARNING("Dooming entry because Close() failed!!!");
+ Doom();
+ }
+ outputWrapper = nullptr;
+ }
+
+ for (uint32_t i = 0 ; i < inputWrappers.Length() ; i++)
+ inputWrappers[i]->Close();
+
+ inputWrappers.Clear();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX perhaps closing descriptors should clear/sever transports
+
+ // tell nsCacheService we're going away
+ nsCacheService::CloseDescriptor(this);
+ NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null");
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetMetaDataElement(const char *key, char **result)
+{
+ NS_ENSURE_ARG_POINTER(key);
+ *result = nullptr;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ const char *value;
+
+ value = mCacheEntry->GetMetaDataElement(key);
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = NS_strdup(value);
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetMetaDataElement(const char *key, const char *value)
+{
+ NS_ENSURE_ARG_POINTER(key);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ // XXX allow null value, for clearing key?
+
+ nsresult rv = mCacheEntry->SetMetaDataElement(key, value);
+ if (NS_SUCCEEDED(rv))
+ mCacheEntry->TouchMetaData();
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor * visitor)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_VISITMETADATA));
+ // XXX check callers, we're calling out of module
+ NS_ENSURE_ARG_POINTER(visitor);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->VisitMetaDataElements(visitor);
+}
+
+
+/******************************************************************************
+ * nsCacheInputStream - a wrapper for nsIInputStream keeps the cache entry
+ * open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsInputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::LazyInit()
+{
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode,
+ mStartOffset,
+ getter_AddRefs(mInput));
+
+ CACHE_LOG_DEBUG(("nsInputStreamWrapper::LazyInit "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+ mDescriptor, this, mInput.get(), int(rv)));
+
+ if (NS_FAILED(rv)) return rv;
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::EnsureInit()
+{
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+void nsCacheEntryDescriptor::
+nsInputStreamWrapper::CloseInternal()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mInput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mInput = nullptr;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Close_Locked()
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mInput->Close();
+ } else {
+ NS_ASSERTION(!mInput,
+ "Shouldn't have mInput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Available(uint64_t *avail)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mInput->Available(avail);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Read_Locked(buf, count, countRead);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Read_Locked(char *buf, uint32_t count, uint32_t *countRead)
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv))
+ rv = mInput->Read(buf, count, countRead);
+
+ CACHE_LOG_DEBUG(("nsInputStreamWrapper::Read "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+ mDescriptor, this, mInput.get(), rv));
+
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // cache stream not buffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::IsNonBlocking(bool *result)
+{
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDecompressInputStreamWrapper - an input stream wrapper that decompresses
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(
+ NSDECOMPRESSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsDecompressInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::Read(char * buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mZstream.next_out = (Bytef*)buf;
+ mZstream.avail_out = count;
+
+ if (mReadBufferLen < count) {
+ // Allocate a buffer for reading from the input stream. This will
+ // determine the max number of compressed bytes read from the
+ // input stream at one time. Making the buffer size proportional
+ // to the request size is not necessary, but helps minimize the
+ // number of read requests to the input stream.
+ uint32_t newBufLen = std::max(count, (uint32_t)kMinDecompressReadBufLen);
+ unsigned char* newBuf;
+ newBuf = (unsigned char*)moz_xrealloc(mReadBuffer,
+ newBufLen);
+ if (newBuf) {
+ mReadBuffer = newBuf;
+ mReadBufferLen = newBufLen;
+ }
+ if (!mReadBuffer) {
+ mReadBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // read and inflate data until the output buffer is full, or
+ // there is no more data to read
+ while (NS_SUCCEEDED(rv) &&
+ zerr == Z_OK &&
+ mZstream.avail_out > 0 &&
+ count > 0) {
+ if (mZstream.avail_in == 0) {
+ rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer,
+ mReadBufferLen,
+ &mZstream.avail_in);
+ if (NS_FAILED(rv) || !mZstream.avail_in) {
+ break;
+ }
+ mZstream.next_in = mReadBuffer;
+ }
+ zerr = inflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_END) {
+ // The compressed data may have been stored in multiple
+ // chunks/streams. To allow for this case, re-initialize
+ // the inflate stream and continue decompressing from
+ // the next byte.
+ Bytef * saveNextIn = mZstream.next_in;
+ unsigned int saveAvailIn = mZstream.avail_in;
+ Bytef * saveNextOut = mZstream.next_out;
+ unsigned int saveAvailOut = mZstream.avail_out;
+ inflateReset(&mZstream);
+ mZstream.next_in = saveNextIn;
+ mZstream.avail_in = saveAvailIn;
+ mZstream.next_out = saveNextOut;
+ mZstream.avail_out = saveAvailOut;
+ zerr = Z_OK;
+ } else if (zerr != Z_OK) {
+ rv = NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ *countRead = count - mZstream.avail_out;
+ }
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ EndZstream();
+ if (mReadBuffer) {
+ free(mReadBuffer);
+ mReadBuffer = 0;
+ mReadBufferLen = 0;
+ }
+ return nsInputStreamWrapper::Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::InitZstream()
+{
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded)
+ return NS_ERROR_FAILURE;
+
+ // Initialize zlib inflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ mZstream.next_out = Z_NULL;
+ mZstream.avail_out = 0;
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+ if (inflateInit(&mZstream) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mStreamInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::EndZstream()
+{
+ if (mStreamInitialized && !mStreamEnded) {
+ inflateEnd(&mZstream);
+ mStreamInitialized = false;
+ mStreamEnded = true;
+ }
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of
+ * data written to a cache entry.
+ * - also keeps the cache entry open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsOutputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor)
+ mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::LazyInit()
+{
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ASSERTION(mOutput == nullptr, "mOutput set in LazyInit");
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset,
+ getter_AddRefs(stream));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCacheDevice* device = cacheEntry->CacheDevice();
+ if (device) {
+ // the entry has been truncated to mStartOffset bytes, inform device
+ int32_t size = cacheEntry->DataSize();
+ rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size);
+ if (NS_SUCCEEDED(rv))
+ cacheEntry->SetDataSize(mStartOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If anything above failed, clean up internal state and get out of here
+ // (see bug #654926)...
+ if (NS_FAILED(rv)) {
+ nsCacheService::ReleaseObject_Locked(stream.forget().take());
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ mInitialized = false;
+ return rv;
+ }
+
+ mOutput = stream;
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::EnsureInit()
+{
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::OnWrite(uint32_t count)
+{
+ if (count > INT32_MAX) return NS_ERROR_UNEXPECTED;
+ return mDescriptor->RequestDataSizeChange((int32_t)count);
+}
+
+void nsCacheEntryDescriptor::
+nsOutputStreamWrapper::CloseInternal()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mOutput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mOutput = nullptr;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Close_Locked()
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mOutput->Close();
+ } else {
+ NS_ASSERTION(!mOutput,
+ "Shouldn't have mOutput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Flush()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Flush();
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Write(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return Write_Locked(buf, count, result);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Write_Locked(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = OnWrite(count);
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Write(buf, count, result);
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::WriteFrom(nsIInputStream * inStr,
+ uint32_t count,
+ uint32_t * result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::WriteSegments(nsReadSegmentFun reader,
+ void * closure,
+ uint32_t count,
+ uint32_t * result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::IsNonBlocking(bool *result)
+{
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsCompressOutputStreamWrapper - an output stream wrapper that compresses
+ * data before it is written
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSCOMPRESSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsCompressOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor)
+ mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::Write(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!mWriteBuffer) {
+ // Once allocated, this buffer is referenced by the zlib stream and
+ // cannot be grown. We use 2x(initial write request) to approximate
+ // a stream buffer size proportional to request buffers.
+ mWriteBufferLen = std::max(count*2, (uint32_t)kMinCompressWriteBufLen);
+ mWriteBuffer = (unsigned char*)moz_xmalloc(mWriteBufferLen);
+ if (!mWriteBuffer) {
+ mWriteBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ }
+
+ // Compress (deflate) the requested buffer. Keep going
+ // until the entire buffer has been deflated.
+ mZstream.avail_in = count;
+ mZstream.next_in = (Bytef*)buf;
+ while (mZstream.avail_in > 0) {
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_ERROR) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return NS_ERROR_FAILURE;
+ }
+ // Note: Z_BUF_ERROR is non-fatal and sometimes expected here.
+
+ // If the compression stream output buffer is filled, write
+ // it out to the underlying stream wrapper.
+ if (mZstream.avail_out == 0) {
+ rv = WriteBuffer();
+ if (NS_FAILED(rv)) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return rv;
+ }
+ }
+ }
+ *result = count;
+ mUncompressedCount += *result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult retval = NS_OK;
+ nsresult rv;
+ int zerr = 0;
+
+ if (mStreamInitialized) {
+ // complete compression of any data remaining in the zlib stream
+ do {
+ zerr = deflate(&mZstream, Z_FINISH);
+ rv = WriteBuffer();
+ if (NS_FAILED(rv))
+ retval = rv;
+ } while (zerr == Z_OK && rv == NS_OK);
+ deflateEnd(&mZstream);
+ mStreamInitialized = false;
+ }
+ // Do not allow to initialize stream after calling Close().
+ mStreamEnded = true;
+
+ if (mDescriptor->CacheEntry()) {
+ nsAutoCString uncompressedLenStr;
+ rv = mDescriptor->GetMetaDataElement("uncompressed-len",
+ getter_Copies(uncompressedLenStr));
+ if (NS_SUCCEEDED(rv)) {
+ int32_t oldCount = uncompressedLenStr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mUncompressedCount += oldCount;
+ }
+ }
+ uncompressedLenStr.Adopt(0);
+ uncompressedLenStr.AppendInt(mUncompressedCount);
+ rv = mDescriptor->SetMetaDataElement("uncompressed-len",
+ uncompressedLenStr.get());
+ if (NS_FAILED(rv))
+ retval = rv;
+ }
+
+ if (mWriteBuffer) {
+ free(mWriteBuffer);
+ mWriteBuffer = 0;
+ mWriteBufferLen = 0;
+ }
+
+ rv = nsOutputStreamWrapper::Close_Locked();
+ if (NS_FAILED(rv))
+ retval = rv;
+
+ return retval;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::InitZstream()
+{
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded)
+ return NS_ERROR_FAILURE;
+
+ // Determine compression level: Aggressive compression
+ // may impact performance on mobile devices, while a
+ // lower compression level still provides substantial
+ // space savings for many text streams.
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+
+ // Initialize zlib deflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ if (deflateInit2(&mZstream, compressionLevel, Z_DEFLATED,
+ MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+
+ mStreamInitialized = true;
+
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::WriteBuffer()
+{
+ uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out;
+ uint32_t result = 0;
+ nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
+ (const char *)mWriteBuffer, bytesToWrite, &result);
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ return rv;
+}
+
diff --git a/netwerk/cache/nsCacheEntryDescriptor.h b/netwerk/cache/nsCacheEntryDescriptor.h
new file mode 100644
index 000000000..64f900811
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.h
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsCacheEntryDescriptor_h_
+#define _nsCacheEntryDescriptor_h_
+
+#include "nsICacheEntryDescriptor.h"
+#include "nsCacheEntry.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCacheService.h"
+#include "zlib.h"
+#include "mozilla/Mutex.h"
+
+/******************************************************************************
+* nsCacheEntryDescriptor
+*******************************************************************************/
+class nsCacheEntryDescriptor final :
+ public PRCList,
+ public nsICacheEntryDescriptor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYDESCRIPTOR
+ NS_DECL_NSICACHEENTRYINFO
+
+ friend class nsAsyncDoomEvent;
+ friend class nsCacheService;
+
+ nsCacheEntryDescriptor(nsCacheEntry * entry, nsCacheAccessMode mode);
+
+ /**
+ * utility method to attempt changing data size of associated entry
+ */
+ nsresult RequestDataSizeChange(int32_t deltaSize);
+
+ /**
+ * methods callbacks for nsCacheService
+ */
+ nsCacheEntry * CacheEntry(void) { return mCacheEntry; }
+ bool ClearCacheEntry(void)
+ {
+ NS_ASSERTION(mInputWrappers.IsEmpty(), "Bad state");
+ NS_ASSERTION(!mOutputWrapper, "Bad state");
+
+ bool doomEntry = false;
+ bool asyncDoomPending;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ asyncDoomPending = mAsyncDoomPending;
+ }
+
+ if (asyncDoomPending && mCacheEntry) {
+ doomEntry = true;
+ mDoomedOnClose = true;
+ }
+ mCacheEntry = nullptr;
+
+ return doomEntry;
+ }
+
+private:
+ virtual ~nsCacheEntryDescriptor();
+
+ /*************************************************************************
+ * input stream wrapper class -
+ *
+ * The input stream wrapper references the descriptor, but the descriptor
+ * doesn't need any references to the stream wrapper.
+ *************************************************************************/
+ class nsInputStreamWrapper : public nsIInputStream {
+ friend class nsCacheEntryDescriptor;
+
+ private:
+ nsCacheEntryDescriptor * mDescriptor;
+ nsCOMPtr<nsIInputStream> mInput;
+ uint32_t mStartOffset;
+ bool mInitialized;
+ mozilla::Mutex mLock;
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ nsInputStreamWrapper(nsCacheEntryDescriptor * desc, uint32_t off)
+ : mDescriptor(desc)
+ , mStartOffset(off)
+ , mInitialized(false)
+ , mLock("nsInputStreamWrapper.mLock")
+ {
+ NS_ADDREF(mDescriptor);
+ }
+
+ private:
+ virtual ~nsInputStreamWrapper()
+ {
+ NS_IF_RELEASE(mDescriptor);
+ }
+
+ nsresult LazyInit();
+ nsresult EnsureInit();
+ nsresult Read_Locked(char *buf, uint32_t count, uint32_t *countRead);
+ nsresult Close_Locked();
+ void CloseInternal();
+ };
+
+
+ class nsDecompressInputStreamWrapper : public nsInputStreamWrapper {
+ private:
+ unsigned char* mReadBuffer;
+ uint32_t mReadBufferLen;
+ z_stream mZstream;
+ bool mStreamInitialized;
+ bool mStreamEnded;
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsDecompressInputStreamWrapper(nsCacheEntryDescriptor * desc,
+ uint32_t off)
+ : nsInputStreamWrapper(desc, off)
+ , mReadBuffer(0)
+ , mReadBufferLen(0)
+ , mStreamInitialized(false)
+ , mStreamEnded(false)
+ {
+ }
+ NS_IMETHOD Read(char* buf, uint32_t count, uint32_t * result) override;
+ NS_IMETHOD Close() override;
+ private:
+ virtual ~nsDecompressInputStreamWrapper()
+ {
+ Close();
+ }
+ nsresult InitZstream();
+ nsresult EndZstream();
+ };
+
+
+ /*************************************************************************
+ * output stream wrapper class -
+ *
+ * The output stream wrapper references the descriptor, but the descriptor
+ * doesn't need any references to the stream wrapper.
+ *************************************************************************/
+ class nsOutputStreamWrapper : public nsIOutputStream {
+ friend class nsCacheEntryDescriptor;
+
+ protected:
+ nsCacheEntryDescriptor * mDescriptor;
+ nsCOMPtr<nsIOutputStream> mOutput;
+ uint32_t mStartOffset;
+ bool mInitialized;
+ mozilla::Mutex mLock;
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsOutputStreamWrapper(nsCacheEntryDescriptor * desc, uint32_t off)
+ : mDescriptor(desc)
+ , mStartOffset(off)
+ , mInitialized(false)
+ , mLock("nsOutputStreamWrapper.mLock")
+ {
+ NS_ADDREF(mDescriptor); // owning ref
+ }
+
+ private:
+ virtual ~nsOutputStreamWrapper()
+ {
+ Close();
+
+ NS_ASSERTION(!mOutput, "Bad state");
+ NS_ASSERTION(!mDescriptor, "Bad state");
+ }
+
+ nsresult LazyInit();
+ nsresult EnsureInit();
+ nsresult OnWrite(uint32_t count);
+ nsresult Write_Locked(const char * buf,
+ uint32_t count,
+ uint32_t * result);
+ nsresult Close_Locked();
+ void CloseInternal();
+ };
+
+
+ class nsCompressOutputStreamWrapper : public nsOutputStreamWrapper {
+ private:
+ unsigned char* mWriteBuffer;
+ uint32_t mWriteBufferLen;
+ z_stream mZstream;
+ bool mStreamInitialized;
+ bool mStreamEnded;
+ uint32_t mUncompressedCount;
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsCompressOutputStreamWrapper(nsCacheEntryDescriptor * desc,
+ uint32_t off)
+ : nsOutputStreamWrapper(desc, off)
+ , mWriteBuffer(0)
+ , mWriteBufferLen(0)
+ , mStreamInitialized(false)
+ , mStreamEnded(false)
+ , mUncompressedCount(0)
+ {
+ }
+ NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t * result) override;
+ NS_IMETHOD Close() override;
+ private:
+ virtual ~nsCompressOutputStreamWrapper()
+ {
+ Close();
+ }
+ nsresult InitZstream();
+ nsresult WriteBuffer();
+ };
+
+ private:
+ /**
+ * nsCacheEntryDescriptor data members
+ */
+ nsCacheEntry * mCacheEntry; // we are a child of the entry
+ nsCacheAccessMode mAccessGranted;
+ nsTArray<nsInputStreamWrapper*> mInputWrappers;
+ nsOutputStreamWrapper * mOutputWrapper;
+ mozilla::Mutex mLock;
+ bool mAsyncDoomPending;
+ bool mDoomedOnClose;
+ bool mClosingDescriptor;
+};
+
+
+#endif // _nsCacheEntryDescriptor_h_
diff --git a/netwerk/cache/nsCacheMetaData.cpp b/netwerk/cache/nsCacheMetaData.cpp
new file mode 100644
index 000000000..0df867d83
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCacheMetaData.h"
+#include "nsICacheEntryDescriptor.h"
+
+const char *
+nsCacheMetaData::GetElement(const char * key)
+{
+ const char * data = mBuffer;
+ const char * limit = mBuffer + mMetaSize;
+
+ while (data < limit) {
+ // Point to the value part
+ const char * value = data + strlen(data) + 1;
+ MOZ_ASSERT(value < limit, "Cache Metadata corrupted");
+ if (strcmp(data, key) == 0)
+ return value;
+
+ // Skip value part
+ data = value + strlen(value) + 1;
+ }
+ MOZ_ASSERT(data == limit, "Metadata corrupted");
+ return nullptr;
+}
+
+
+nsresult
+nsCacheMetaData::SetElement(const char * key,
+ const char * value)
+{
+ const uint32_t keySize = strlen(key) + 1;
+ char * pos = (char *)GetElement(key);
+
+ if (!value) {
+ // No value means remove the key/value pair completely, if existing
+ if (pos) {
+ uint32_t oldValueSize = strlen(pos) + 1;
+ uint32_t offset = pos - mBuffer;
+ uint32_t remainder = mMetaSize - (offset + oldValueSize);
+
+ memmove(pos - keySize, pos + oldValueSize, remainder);
+ mMetaSize -= keySize + oldValueSize;
+ }
+ return NS_OK;
+ }
+
+ const uint32_t valueSize = strlen(value) + 1;
+ uint32_t newSize = mMetaSize + valueSize;
+ if (pos) {
+ const uint32_t oldValueSize = strlen(pos) + 1;
+ const uint32_t offset = pos - mBuffer;
+ const uint32_t remainder = mMetaSize - (offset + oldValueSize);
+
+ // Update the value in place
+ newSize -= oldValueSize;
+ nsresult rv = EnsureBuffer(newSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the remainder to the right place
+ pos = mBuffer + offset;
+ memmove(pos + valueSize, pos + oldValueSize, remainder);
+ } else {
+ // allocate new meta data element
+ newSize += keySize;
+ nsresult rv = EnsureBuffer(newSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add after last element
+ pos = mBuffer + mMetaSize;
+ memcpy(pos, key, keySize);
+ pos += keySize;
+ }
+
+ // Update value
+ memcpy(pos, value, valueSize);
+ mMetaSize = newSize;
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheMetaData::FlattenMetaData(char * buffer, uint32_t bufSize)
+{
+ if (mMetaSize > bufSize) {
+ NS_ERROR("buffer size too small for meta data.");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(buffer, mBuffer, mMetaSize);
+ return NS_OK;
+}
+
+nsresult
+nsCacheMetaData::UnflattenMetaData(const char * data, uint32_t size)
+{
+ if (data && size) {
+ // Check if the metadata ends with a zero byte.
+ if (data[size-1] != '\0') {
+ NS_ERROR("Cache MetaData is not null terminated");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // Check that there are an even number of zero bytes
+ // to match the pattern { key \0 value \0 }
+ bool odd = false;
+ for (uint32_t i = 0; i < size; i++) {
+ if (data[i] == '\0')
+ odd = !odd;
+ }
+ if (odd) {
+ NS_ERROR("Cache MetaData is malformed");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = EnsureBuffer(size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ memcpy(mBuffer, data, size);
+ mMetaSize = size;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsCacheMetaData::VisitElements(nsICacheMetaDataVisitor * visitor)
+{
+ const char * data = mBuffer;
+ const char * limit = mBuffer + mMetaSize;
+
+ while (data < limit) {
+ const char * key = data;
+ // Skip key part
+ data += strlen(data) + 1;
+ MOZ_ASSERT(data < limit, "Metadata corrupted");
+ bool keepGoing;
+ nsresult rv = visitor->VisitMetaDataElement(key, data, &keepGoing);
+ if (NS_FAILED(rv) || !keepGoing)
+ return NS_OK;
+
+ // Skip value part
+ data += strlen(data) + 1;
+ }
+ MOZ_ASSERT(data == limit, "Metadata corrupted");
+ return NS_OK;
+}
+
+nsresult
+nsCacheMetaData::EnsureBuffer(uint32_t bufSize)
+{
+ if (mBufferSize < bufSize) {
+ char * buf = (char *)realloc(mBuffer, bufSize);
+ if (!buf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBuffer = buf;
+ mBufferSize = bufSize;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheMetaData.h b/netwerk/cache/nsCacheMetaData.h
new file mode 100644
index 000000000..8ac19125a
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheMetaData_h_
+#define _nsCacheMetaData_h_
+
+#include "nspr.h"
+#include "nscore.h"
+
+class nsICacheMetaDataVisitor;
+
+class nsCacheMetaData {
+public:
+ nsCacheMetaData() : mBuffer(nullptr), mBufferSize(0), mMetaSize(0) { }
+
+ ~nsCacheMetaData() {
+ mBufferSize = mMetaSize = 0;
+ free(mBuffer);
+ mBuffer = nullptr;
+ }
+
+ const char * GetElement(const char * key);
+
+ nsresult SetElement(const char * key, const char * value);
+
+ uint32_t Size(void) { return mMetaSize; }
+
+ nsresult FlattenMetaData(char * buffer, uint32_t bufSize);
+
+ nsresult UnflattenMetaData(const char * buffer, uint32_t bufSize);
+
+ nsresult VisitElements(nsICacheMetaDataVisitor * visitor);
+
+private:
+ nsresult EnsureBuffer(uint32_t size);
+
+ char * mBuffer;
+ uint32_t mBufferSize;
+ uint32_t mMetaSize;
+};
+
+#endif // _nsCacheMetaData_h
diff --git a/netwerk/cache/nsCacheRequest.h b/netwerk/cache/nsCacheRequest.h
new file mode 100644
index 000000000..6681dec67
--- /dev/null
+++ b/netwerk/cache/nsCacheRequest.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheRequest_h_
+#define _nsCacheRequest_h_
+
+#include "nspr.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsICache.h"
+#include "nsICacheListener.h"
+#include "nsCacheSession.h"
+#include "nsCacheService.h"
+
+
+class nsCacheRequest : public PRCList
+{
+ typedef mozilla::CondVar CondVar;
+ typedef mozilla::MutexAutoLock MutexAutoLock;
+ typedef mozilla::Mutex Mutex;
+
+private:
+ friend class nsCacheService;
+ friend class nsCacheEntry;
+ friend class nsProcessRequestEvent;
+
+ nsCacheRequest( const nsACString & key,
+ nsICacheListener * listener,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsCacheSession * session)
+ : mKey(key),
+ mInfo(0),
+ mListener(listener),
+ mLock("nsCacheRequest.mLock"),
+ mCondVar(mLock, "nsCacheRequest.mCondVar"),
+ mProfileDir(session->ProfileDir())
+ {
+ MOZ_COUNT_CTOR(nsCacheRequest);
+ PR_INIT_CLIST(this);
+ SetAccessRequested(accessRequested);
+ SetStoragePolicy(session->StoragePolicy());
+ if (session->IsStreamBased()) MarkStreamBased();
+ if (session->WillDoomEntriesIfExpired()) MarkDoomEntriesIfExpired();
+ if (session->IsPrivate()) MarkPrivate();
+ if (blockingMode == nsICache::BLOCKING) MarkBlockingMode();
+ MarkWaitingForValidation();
+ NS_IF_ADDREF(mListener);
+ }
+
+ ~nsCacheRequest()
+ {
+ MOZ_COUNT_DTOR(nsCacheRequest);
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "request still on a list");
+
+ if (mListener)
+ nsCacheService::ReleaseObject_Locked(mListener, mThread);
+ }
+
+ /**
+ * Simple Accessors
+ */
+ enum CacheRequestInfo {
+ eStoragePolicyMask = 0x000000FF,
+ eStreamBasedMask = 0x00000100,
+ ePrivateMask = 0x00000200,
+ eDoomEntriesIfExpiredMask = 0x00001000,
+ eBlockingModeMask = 0x00010000,
+ eWaitingForValidationMask = 0x00100000,
+ eAccessRequestedMask = 0xFF000000
+ };
+
+ void SetAccessRequested(nsCacheAccessMode mode)
+ {
+ NS_ASSERTION(mode <= 0xFF, "too many bits in nsCacheAccessMode");
+ mInfo &= ~eAccessRequestedMask;
+ mInfo |= mode << 24;
+ }
+
+ nsCacheAccessMode AccessRequested()
+ {
+ return (nsCacheAccessMode)((mInfo >> 24) & 0xFF);
+ }
+
+ void MarkStreamBased() { mInfo |= eStreamBasedMask; }
+ bool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; }
+
+
+ void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; }
+ bool WillDoomEntriesIfExpired() { return (0 != (mInfo & eDoomEntriesIfExpiredMask)); }
+
+ void MarkBlockingMode() { mInfo |= eBlockingModeMask; }
+ bool IsBlocking() { return (0 != (mInfo & eBlockingModeMask)); }
+ bool IsNonBlocking() { return !(mInfo & eBlockingModeMask); }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy)
+ {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mInfo &= ~eStoragePolicyMask; // clear storage policy bits
+ mInfo |= policy; // or in new bits
+ }
+
+ nsCacheStoragePolicy StoragePolicy()
+ {
+ return (nsCacheStoragePolicy)(mInfo & eStoragePolicyMask);
+ }
+
+ void MarkPrivate() { mInfo |= ePrivateMask; }
+ void MarkPublic() { mInfo &= ~ePrivateMask; }
+ bool IsPrivate() { return (mInfo & ePrivateMask) != 0; }
+
+ void MarkWaitingForValidation() { mInfo |= eWaitingForValidationMask; }
+ void DoneWaitingForValidation() { mInfo &= ~eWaitingForValidationMask; }
+ bool WaitingForValidation()
+ {
+ return (mInfo & eWaitingForValidationMask) != 0;
+ }
+
+ nsresult
+ WaitForValidation(void)
+ {
+ if (!WaitingForValidation()) { // flag already cleared
+ MarkWaitingForValidation(); // set up for next time
+ return NS_OK; // early exit;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ while (WaitingForValidation()) {
+ mCondVar.Wait();
+ }
+ MarkWaitingForValidation(); // set up for next time
+ }
+ return NS_OK;
+ }
+
+ void WakeUp(void) {
+ DoneWaitingForValidation();
+ MutexAutoLock lock(mLock);
+ mCondVar.Notify();
+ }
+
+ /**
+ * Data members
+ */
+ nsCString mKey;
+ uint32_t mInfo;
+ nsICacheListener * mListener; // strong ref
+ nsCOMPtr<nsIThread> mThread;
+ Mutex mLock;
+ CondVar mCondVar;
+ nsCOMPtr<nsIFile> mProfileDir;
+};
+
+#endif // _nsCacheRequest_h_
diff --git a/netwerk/cache/nsCacheService.cpp b/netwerk/cache/nsCacheService.cpp
new file mode 100644
index 000000000..bab67e104
--- /dev/null
+++ b/netwerk/cache/nsCacheService.cpp
@@ -0,0 +1,3209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+#include "necko-config.h"
+
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheRequest.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheDevice.h"
+#include "nsMemoryCacheDevice.h"
+#include "nsICacheVisitor.h"
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheUtils.h"
+#include "../cache2/CacheObserver.h"
+
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIFile.h"
+#include "nsIOService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsDeleteDir.h"
+#include "nsNetCID.h"
+#include <math.h> // for log()
+#include "mozilla/Services.h"
+#include "nsITimer.h"
+#include "mozIStorageService.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+/******************************************************************************
+ * nsCacheProfilePrefObserver
+ *****************************************************************************/
+#define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
+#define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
+#define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
+ "browser.cache.disk.smart_size.first_run"
+#define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
+ "browser.cache.disk.smart_size.enabled"
+#define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
+#define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
+#define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
+#define DISK_CACHE_CAPACITY 256000
+
+#define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
+ "browser.cache.disk.smart_size.use_old_max"
+
+#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
+#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
+#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
+#define OFFLINE_CACHE_CAPACITY 512000
+
+#define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
+#define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
+#define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
+
+#define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
+#define CACHE_COMPRESSION_LEVEL 1
+
+#define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
+#define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
+
+static const char * observerList[] = {
+ "profile-before-change",
+ "profile-do-change",
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ "last-pb-context-exited",
+ "suspend_process_notification",
+ "resume_process_notification"
+};
+
+static const char * prefList[] = {
+ DISK_CACHE_ENABLE_PREF,
+ DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ DISK_CACHE_CAPACITY_PREF,
+ DISK_CACHE_DIR_PREF,
+ DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ OFFLINE_CACHE_ENABLE_PREF,
+ OFFLINE_CACHE_CAPACITY_PREF,
+ OFFLINE_CACHE_DIR_PREF,
+ MEMORY_CACHE_ENABLE_PREF,
+ MEMORY_CACHE_CAPACITY_PREF,
+ MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ CACHE_COMPRESSION_LEVEL_PREF,
+ SANITIZE_ON_SHUTDOWN_PREF,
+ CLEAR_ON_SHUTDOWN_PREF
+};
+
+// Cache sizes, in KB
+const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
+#ifdef ANDROID
+const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+#else
+const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
+const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
+#endif
+// Default cache size was 50 MB for many years until FF 4:
+const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
+
+class nsCacheProfilePrefObserver : public nsIObserver
+{
+ virtual ~nsCacheProfilePrefObserver() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsCacheProfilePrefObserver()
+ : mHaveProfile(false)
+ , mDiskCacheEnabled(false)
+ , mDiskCacheCapacity(0)
+ , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mSmartSizeEnabled(false)
+ , mShouldUseOldMaxSmartSize(false)
+ , mOfflineCacheEnabled(false)
+ , mOfflineCacheCapacity(0)
+ , mMemoryCacheEnabled(true)
+ , mMemoryCacheCapacity(-1)
+ , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
+ , mSanitizeOnShutdown(false)
+ , mClearCacheOnShutdown(false)
+ {
+ }
+
+ nsresult Install();
+ void Remove();
+ nsresult ReadPrefs(nsIPrefBranch* branch);
+
+ bool DiskCacheEnabled();
+ int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
+ void SetDiskCacheCapacity(int32_t);
+ int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
+ nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
+ bool SmartSizeEnabled() { return mSmartSizeEnabled; }
+
+ bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
+ void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
+
+ bool OfflineCacheEnabled();
+ int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
+ nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
+
+ bool MemoryCacheEnabled();
+ int32_t MemoryCacheCapacity();
+ int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
+
+ int32_t CacheCompressionLevel();
+
+ bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
+
+ static uint32_t GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize);
+
+ bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
+
+private:
+ bool mHaveProfile;
+
+ bool mDiskCacheEnabled;
+ int32_t mDiskCacheCapacity; // in kilobytes
+ int32_t mDiskCacheMaxEntrySize; // in kilobytes
+ nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
+ bool mSmartSizeEnabled;
+
+ bool mShouldUseOldMaxSmartSize;
+
+ bool mOfflineCacheEnabled;
+ int32_t mOfflineCacheCapacity; // in kilobytes
+ nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
+
+ bool mMemoryCacheEnabled;
+ int32_t mMemoryCacheCapacity; // in kilobytes
+ int32_t mMemoryCacheMaxEntrySize; // in kilobytes
+
+ int32_t mCacheCompressionLevel;
+
+ bool mSanitizeOnShutdown;
+ bool mClearCacheOnShutdown;
+};
+
+NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
+
+class nsSetDiskSmartSizeCallback final : public nsITimerCallback
+{
+ ~nsSetDiskSmartSizeCallback() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ if (nsCacheService::gService) {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+ nsCacheService::gService->mSmartSizeTimer = nullptr;
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
+
+// Runnable sent to main thread after the cache IO thread calculates available
+// disk space, so that there is no race in setting mDiskCacheCapacity.
+class nsSetSmartSizeEvent: public Runnable
+{
+public:
+ explicit nsSetSmartSizeEvent(int32_t smartSize)
+ : mSmartSize(smartSize) {}
+
+ NS_IMETHOD Run()
+ {
+ NS_ASSERTION(NS_IsMainThread(),
+ "Setting smart size data off the main thread");
+
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Ensure smart sizing wasn't switched off while event was pending.
+ // It is safe to access the observer without the lock since we are
+ // on the main thread and the value changes only on the main thread.
+ if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
+ return NS_OK;
+
+ nsCacheService::SetDiskCacheCapacity(mSmartSize);
+
+ nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!ps ||
+ NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
+ NS_WARNING("Failed to set smart size pref");
+
+ return NS_OK;
+ }
+
+private:
+ int32_t mSmartSize;
+};
+
+
+// Runnable sent from main thread to cacheIO thread
+class nsGetSmartSizeEvent: public Runnable
+{
+public:
+ nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+ : mCachePath(cachePath)
+ , mCurrentSize(currentSize)
+ , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
+ {}
+
+ // Calculates user's disk space available on a background thread and
+ // dispatches this value back to the main thread.
+ NS_IMETHOD Run() override
+ {
+ uint32_t size;
+ size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
+ mCurrentSize,
+ mShouldUseOldMaxSmartSize);
+ NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
+ return NS_OK;
+ }
+
+private:
+ nsString mCachePath;
+ uint32_t mCurrentSize;
+ bool mShouldUseOldMaxSmartSize;
+};
+
+class nsBlockOnCacheThreadEvent : public Runnable {
+public:
+ nsBlockOnCacheThreadEvent()
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
+ CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
+ nsCacheService::gService->mNotified = true;
+ nsCacheService::gService->mCondVar.Notify();
+ return NS_OK;
+ }
+};
+
+
+nsresult
+nsCacheProfilePrefObserver::Install()
+{
+ // install profile-change observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv, rv2 = NS_OK;
+ for (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ rv = observerService->AddObserver(this, observerList[i], false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // install preferences observer
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) return NS_ERROR_FAILURE;
+
+ for (unsigned int i=0; i<ArrayLength(prefList); i++) {
+ rv = branch->AddObserver(prefList[i], this, false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // Determine if we have a profile already
+ // Install() is called *after* the profile-after-change notification
+ // when there is only a single profile, or it is specified on the
+ // commandline at startup.
+ // In that case, we detect the presence of a profile by the existence
+ // of the NS_APP_USER_PROFILE_50_DIR directory.
+
+ nsCOMPtr<nsIFile> directory;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(directory));
+ if (NS_SUCCEEDED(rv))
+ mHaveProfile = true;
+
+ rv = ReadPrefs(branch);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv2;
+}
+
+
+void
+nsCacheProfilePrefObserver::Remove()
+{
+ // remove Observer Service observers
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ for (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ obs->RemoveObserver(this, observerList[i]);
+ }
+ }
+
+ // remove Pref Service observers
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return;
+ for (unsigned int i=0; i<ArrayLength(prefList); i++)
+ prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
+}
+
+void
+nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
+{
+ mDiskCacheCapacity = std::max(0, capacity);
+}
+
+
+NS_IMETHODIMP
+nsCacheProfilePrefObserver::Observe(nsISupports * subject,
+ const char * topic,
+ const char16_t * data_unicode)
+{
+ nsresult rv;
+ NS_ConvertUTF16toUTF8 data(data_unicode);
+ CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
+
+ if (!nsCacheService::IsInitialized()) {
+ if (!strcmp("resume_process_notification", topic)) {
+ // A suspended process has a closed cache, so re-open it here.
+ nsCacheService::GlobalInstance()->Init();
+ }
+ return NS_OK;
+ }
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ // xpcom going away, shutdown cache service
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-before-change", topic)) {
+ // profile before change
+ mHaveProfile = false;
+
+ // XXX shutdown devices
+ nsCacheService::OnProfileShutdown();
+ } else if (!strcmp("suspend_process_notification", topic)) {
+ // A suspended process may never return, so shutdown the cache to reduce
+ // cache corruption.
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-do-change", topic)) {
+ // profile after change
+ mHaveProfile = true;
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_FAILURE;
+ }
+ (void)ReadPrefs(branch);
+ nsCacheService::OnProfileChanged();
+
+ } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
+
+ // ignore pref changes until we're done switch profiles
+ if (!mHaveProfile)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // which preference changed?
+ if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
+ &mDiskCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+
+ } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+
+ // Update the cache capacity when smart sizing is turned on/off
+ } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
+ // Is the update because smartsizing was turned on, or off?
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ int32_t newCapacity = 0;
+ if (mSmartSizeEnabled) {
+ nsCacheService::SetDiskSmartSize();
+ } else {
+ // Smart sizing switched off: use user specified size
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, newCapacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+ }
+ } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
+ rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+ if (NS_FAILED(rv))
+ return rv;
+ } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
+
+#if 0
+ } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be somekind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ // which preference changed?
+ if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+ if (NS_FAILED(rv)) return rv;
+ nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
+
+ } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv)) return rv;
+ mOfflineCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
+#if 0
+ } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be some kind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
+ &mMemoryCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetMemoryCache();
+
+ } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+ nsCacheService::SetMemoryCache();
+ } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
+ } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+ } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ }
+ } else if (!strcmp("last-pb-context-exited", topic)) {
+ nsCacheService::LeavePrivateBrowsing();
+ }
+
+ return NS_OK;
+}
+
+// Returns default ("smart") size (in KB) of cache, given available disk space
+// (also in KB)
+static uint32_t
+SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
+{
+ uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+
+ if (availKB > 100 * 1024 * 1024)
+ return maxSize; // skip computing if we're over 100 GB
+
+ // Grow/shrink in 10 MB units, deliberately, so that in the common case we
+ // don't shrink cache and evict items every time we startup (it's important
+ // that we don't slow down startup benchmarks).
+ uint32_t sz10MBs = 0;
+ uint32_t avail10MBs = availKB / (1024*10);
+
+ // .5% of space above 25 GB
+ if (avail10MBs > 2500) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
+ avail10MBs = 2500;
+ }
+ // 1% of space between 7GB -> 25 GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
+ avail10MBs = 700;
+ }
+ // 5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
+ avail10MBs = 50;
+ }
+
+#ifdef ANDROID
+ // On Android, smaller/older devices may have very little storage and
+ // device owners may be sensitive to storage footprint: Use a smaller
+ // percentage of available space and a smaller minimum.
+
+ // 20% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
+#else
+ // 40% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+ /* Computes our best guess for the default size of the user's disk cache,
+ * based on the amount of space they have free on their hard drive.
+ * We use a tiered scheme: the more space available,
+ * the larger the disk cache will be. However, we do not want
+ * to enable the disk cache to grow to an unbounded size, so the larger the
+ * user's available space is, the smaller of a percentage we take. We set a
+ * lower bound of 50MB and an upper bound of 1GB.
+ *
+ *@param: None.
+ *@return: The size that the user's disk cache should default to, in kBytes.
+ */
+uint32_t
+nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+{
+ // Check for free space on device where cache directory lives
+ nsresult rv;
+ nsCOMPtr<nsIFile>
+ cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || !cacheDirectory)
+ return DEFAULT_CACHE_SIZE;
+ rv = cacheDirectory->InitWithPath(cachePath);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+ int64_t bytesAvailable;
+ rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+
+ return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
+ currentSize),
+ shouldUseOldMaxSmartSize);
+}
+
+/* Determine if we are permitted to dynamically size the user's disk cache based
+ * on their disk space available. We may do this so long as the pref
+ * smart_size.enabled is true.
+ */
+bool
+nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
+ firstRun)
+{
+ nsresult rv;
+ if (firstRun) {
+ // check if user has set cache size in the past
+ bool userSet;
+ rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
+ if (NS_FAILED(rv)) userSet = true;
+ if (userSet) {
+ int32_t oldCapacity;
+ // If user explicitly set cache size to be smaller than old default
+ // of 50 MB, then keep user's value. Otherwise use smart sizing.
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
+ if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
+ mSmartSizeEnabled = false;
+ branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ mSmartSizeEnabled);
+ return mSmartSizeEnabled;
+ }
+ }
+ // Set manual setting to MAX cache size as starting val for any
+ // adjustment by user: (bug 559942 comment 65)
+ int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+ branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
+ }
+
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ mSmartSizeEnabled = false;
+ return mSmartSizeEnabled;
+}
+
+
+nsresult
+nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
+{
+ nsresult rv = NS_OK;
+
+ // read disk cache device prefs
+ mDiskCacheEnabled = true; // presume disk cache is enabled
+ (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
+
+ mDiskCacheCapacity = DISK_CACHE_CAPACITY;
+ (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
+ mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
+
+ (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mDiskCacheMaxEntrySize);
+ mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
+
+ (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mDiskCacheParentDirectory));
+
+ (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+
+ if (!mDiskCacheParentDirectory) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the disk cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // try to get the profile directory (there may not be a profile yet)
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ if (!directory)
+ directory = profDir;
+ else if (profDir) {
+ nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
+ "Cache");
+ }
+ }
+ // use file cache in build tree only if asked, to avoid cache dir litter
+ if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+ if (directory)
+ mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+ if (mDiskCacheParentDirectory) {
+ bool firstSmartSizeRun;
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ &firstSmartSizeRun);
+ if (NS_FAILED(rv))
+ firstSmartSizeRun = false;
+ if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
+ // Avoid evictions: use previous cache size until smart size event
+ // updates mDiskCacheCapacity
+ rv = branch->GetIntPref(firstSmartSizeRun ?
+ DISK_CACHE_CAPACITY_PREF :
+ DISK_CACHE_SMART_SIZE_PREF,
+ &mDiskCacheCapacity);
+ if (NS_FAILED(rv))
+ mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
+ }
+
+ if (firstSmartSizeRun) {
+ // It is no longer our first run
+ rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ false);
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed setting first_run pref in ReadPrefs.");
+ }
+ }
+
+ // read offline cache device prefs
+ mOfflineCacheEnabled = true; // presume offline cache is enabled
+ (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+
+ mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
+ (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
+ &mOfflineCacheCapacity);
+ mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
+
+ (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mOfflineCacheParentDirectory));
+
+ if (!mOfflineCacheParentDirectory) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the offline cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // try to get the profile directory (there may not be a profile yet)
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ if (!directory)
+ directory = profDir;
+ else if (profDir) {
+ nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
+ "OfflineCache");
+ }
+ }
+#if DEBUG
+ if (!directory) {
+ // use current process directory during development
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+#endif
+ if (directory)
+ mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+
+ // read memory cache device prefs
+ (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+
+ (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mMemoryCacheMaxEntrySize);
+ mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
+
+ // read cache compression level pref
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+
+ // read cache shutdown sanitization prefs
+ (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+
+ return rv;
+}
+
+nsresult
+nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
+{
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+nsCacheService::SyncWithCacheIOThread()
+{
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
+
+ // dispatch event - it will notify the monitor when it's done
+ nsresult rv =
+ gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // wait until notified, then return
+ gService->mNotified = false;
+ while (!gService->mNotified) {
+ gService->mCondVar.Wait();
+ }
+
+ return NS_OK;
+}
+
+
+bool
+nsCacheProfilePrefObserver::DiskCacheEnabled()
+{
+ if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
+ return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
+}
+
+
+bool
+nsCacheProfilePrefObserver::OfflineCacheEnabled()
+{
+ if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
+ return false;
+
+ return mOfflineCacheEnabled;
+}
+
+
+bool
+nsCacheProfilePrefObserver::MemoryCacheEnabled()
+{
+ if (mMemoryCacheCapacity == 0) return false;
+ return mMemoryCacheEnabled;
+}
+
+
+/**
+ * MemoryCacheCapacity
+ *
+ * If the browser.cache.memory.capacity preference is positive, we use that
+ * value for the amount of memory available for the cache.
+ *
+ * If browser.cache.memory.capacity is zero, the memory cache is disabled.
+ *
+ * If browser.cache.memory.capacity is negative or not present, we use a
+ * formula that grows less than linearly with the amount of system memory,
+ * with an upper limit on the cache size. No matter how much physical RAM is
+ * present, the default cache size would not exceed 32 MB. This maximum would
+ * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
+ *
+ * RAM Cache
+ * --- -----
+ * 32 Mb 2 Mb
+ * 64 Mb 4 Mb
+ * 128 Mb 6 Mb
+ * 256 Mb 10 Mb
+ * 512 Mb 14 Mb
+ * 1024 Mb 18 Mb
+ * 2048 Mb 24 Mb
+ * 4096 Mb 30 Mb
+ *
+ * The equation for this is (for cache size C and memory size K (kbytes)):
+ * x = log2(K) - 14
+ * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
+ * if (C > 32) C = 32
+ */
+
+int32_t
+nsCacheProfilePrefObserver::MemoryCacheCapacity()
+{
+ int32_t capacity = mMemoryCacheCapacity;
+ if (capacity >= 0) {
+ CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
+ return capacity;
+ }
+
+ static uint64_t bytes = PR_GetPhysicalMemorySize();
+ CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
+
+ // If getting the physical memory failed, arbitrarily assume
+ // 32 MB of RAM. We use a low default to have a reasonable
+ // size on all the devices we support.
+ if (bytes == 0)
+ bytes = 32 * 1024 * 1024;
+
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX)
+ bytes = INT64_MAX;
+
+ uint64_t kbytes = bytes >> 10;
+
+ double kBytesD = double(kbytes);
+
+ double x = log(kBytesD)/log(2.0) - 14;
+ if (x > 0) {
+ capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
+ if (capacity > 32)
+ capacity = 32;
+ capacity *= 1024;
+ } else {
+ capacity = 0;
+ }
+
+ return capacity;
+}
+
+int32_t
+nsCacheProfilePrefObserver::CacheCompressionLevel()
+{
+ return mCacheCompressionLevel;
+}
+
+/******************************************************************************
+ * nsProcessRequestEvent
+ *****************************************************************************/
+
+class nsProcessRequestEvent : public Runnable {
+public:
+ explicit nsProcessRequestEvent(nsCacheRequest *aRequest)
+ {
+ mRequest = aRequest;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsresult rv;
+
+ NS_ASSERTION(mRequest->mListener,
+ "Sync OpenCacheEntry() posted to background thread!");
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
+ rv = nsCacheService::gService->ProcessRequest(mRequest,
+ false,
+ nullptr);
+
+ // Don't delete the request if it was queued
+ if (!(mRequest->IsBlocking() &&
+ rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
+ delete mRequest;
+
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsProcessRequestEvent() {}
+
+private:
+ nsCacheRequest *mRequest;
+};
+
+/******************************************************************************
+ * nsDoomEvent
+ *****************************************************************************/
+
+class nsDoomEvent : public Runnable {
+public:
+ nsDoomEvent(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener)
+ {
+ mKey = *session->ClientID();
+ mKey.Append(':');
+ mKey.Append(key);
+ mStoragePolicy = session->StoragePolicy();
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+
+ bool foundActive = true;
+ nsresult status = NS_ERROR_NOT_AVAILABLE;
+ nsCacheEntry *entry;
+ entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
+ if (!entry) {
+ bool collision = false;
+ foundActive = false;
+ entry = nsCacheService::gService->SearchCacheDevices(&mKey,
+ mStoragePolicy,
+ &collision);
+ }
+
+ if (entry) {
+ status = NS_OK;
+ nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
+ }
+
+ if (mListener) {
+ mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCString mKey;
+ nsCacheStoragePolicy mStoragePolicy;
+ nsICacheListener *mListener;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+/******************************************************************************
+ * nsCacheService
+ *****************************************************************************/
+nsCacheService * nsCacheService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
+ nsIMemoryReporter)
+
+nsCacheService::nsCacheService()
+ : mObserver(nullptr),
+ mLock("nsCacheService.mLock"),
+ mCondVar(mLock, "nsCacheService.mCondVar"),
+ mNotified(false),
+ mTimeStampLock("nsCacheService.mTimeStampLock"),
+ mInitialized(false),
+ mClearingEntries(false),
+ mEnableMemoryDevice(true),
+ mEnableDiskDevice(true),
+ mMemoryDevice(nullptr),
+ mDiskDevice(nullptr),
+ mOfflineDevice(nullptr),
+ mTotalEntries(0),
+ mCacheHits(0),
+ mCacheMisses(0),
+ mMaxKeyLength(0),
+ mMaxDataSize(0),
+ mMaxMetaSize(0),
+ mDeactivateFailures(0),
+ mDeactivatedUnboundEntries(0)
+{
+ NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
+ gService = this;
+
+ // create list of cache devices
+ PR_INIT_CLIST(&mDoomedEntries);
+}
+
+nsCacheService::~nsCacheService()
+{
+ if (mInitialized) // Shutdown hasn't been called yet.
+ (void) Shutdown();
+
+ if (mObserver) {
+ mObserver->Remove();
+ NS_RELEASE(mObserver);
+ }
+
+ gService = nullptr;
+}
+
+
+nsresult
+nsCacheService::Init()
+{
+ // Thie method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsCacheService::Init called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
+ if (mInitialized)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ if (mozilla::net::IsNeckoChild()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+
+ mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNamedThread("Cache I/O",
+ getter_AddRefs(mCacheIOThread));
+ if (NS_FAILED(rv)) {
+ NS_RUNTIMEABORT("Can't create cache IO thread");
+ }
+
+ rv = nsDeleteDir::Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't initialize nsDeleteDir");
+ }
+
+ // initialize hashtable for active cache entries
+ mActiveEntries.Init();
+
+ // create profile/preference observer
+ if (!mObserver) {
+ mObserver = new nsCacheProfilePrefObserver();
+ NS_ADDREF(mObserver);
+ mObserver->Install();
+ }
+
+ mEnableDiskDevice = mObserver->DiskCacheEnabled();
+ mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
+ mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
+
+ RegisterWeakMemoryReporter(this);
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+void
+nsCacheService::Shutdown()
+{
+ // This method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
+ }
+
+ nsCOMPtr<nsIThread> cacheIOThread;
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
+
+ bool shouldSanitize = false;
+ nsCOMPtr<nsIFile> parentDir;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized,
+ "can't shutdown nsCacheService unless it has been initialized.");
+ if (!mInitialized)
+ return;
+
+ mClearingEntries = true;
+ DoomActiveEntries(nullptr);
+ }
+
+ CloseAllStreams();
+
+ UnregisterWeakMemoryReporter(this);
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized, "Bad state");
+
+ mInitialized = false;
+
+ // Clear entries
+ ClearDoomList();
+
+ if (mSmartSizeTimer) {
+ mSmartSizeTimer->Cancel();
+ mSmartSizeTimer = nullptr;
+ }
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void) SyncWithCacheIOThread();
+ mActiveEntries.Shutdown();
+
+ // obtain the disk cache directory in case we need to sanitize it
+ parentDir = mObserver->DiskCacheParentDirectory();
+ shouldSanitize = mObserver->SanitizeAtShutdown();
+
+ // deallocate memory and disk caches
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+
+ if (mOfflineDevice)
+ mOfflineDevice->Shutdown();
+
+ NS_IF_RELEASE(mOfflineDevice);
+
+ for (auto iter = mCustomOfflineDevices.Iter();
+ !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ LogCacheStatistics();
+
+ mClearingEntries = false;
+ mCacheIOThread.swap(cacheIOThread);
+ }
+
+ if (cacheIOThread)
+ nsShutdownThread::BlockingShutdown(cacheIOThread);
+
+ if (shouldSanitize) {
+ nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_SUCCEEDED(rv)) {
+ bool exists;
+ if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
+ nsDeleteDir::DeleteDir(parentDir, false);
+ }
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ } else {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ }
+}
+
+
+nsresult
+nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
+{
+ nsresult rv;
+
+ if (aOuter != nullptr)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsCacheService * cacheService = new nsCacheService();
+ if (cacheService == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(cacheService);
+ rv = cacheService->Init();
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheService->QueryInterface(aIID, aResult);
+ }
+ NS_RELEASE(cacheService);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheService::CreateSession(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result)
+{
+ *result = nullptr;
+
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return CreateSessionInternal(clientID, storagePolicy, streamBased, result);
+}
+
+nsresult
+nsCacheService::CreateSessionInternal(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result)
+{
+ RefPtr<nsCacheSession> session =
+ new nsCacheSession(clientID, storagePolicy, streamBased);
+ session.forget(result);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::EvictEntriesForSession(nsCacheSession * session)
+{
+ NS_ASSERTION(gService, "nsCacheService::gService is null.");
+ return gService->EvictEntriesForClient(session->ClientID()->get(),
+ session->StoragePolicy());
+}
+
+namespace {
+
+class EvictionNotifierRunnable : public Runnable
+{
+public:
+ explicit EvictionNotifierRunnable(nsISupports* aSubject)
+ : mSubject(aSubject)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+private:
+ nsCOMPtr<nsISupports> mSubject;
+};
+
+NS_IMETHODIMP
+EvictionNotifierRunnable::Run()
+{
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(mSubject,
+ NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
+ nullptr);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult
+nsCacheService::EvictEntriesForClient(const char * clientID,
+ nsCacheStoragePolicy storagePolicy)
+{
+ RefPtr<EvictionNotifierRunnable> r =
+ new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
+ NS_DispatchToMainThread(r);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
+ nsresult res = NS_OK;
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK) {
+
+ if (mEnableDiskDevice) {
+ nsresult rv = NS_OK;
+ if (!mDiskDevice)
+ rv = CreateDiskDevice();
+ if (mDiskDevice)
+ rv = mDiskDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ // Only clear the offline cache if it has been specifically asked for.
+ if (storagePolicy == nsICache::STORE_OFFLINE) {
+ if (mEnableOfflineDevice) {
+ nsresult rv = NS_OK;
+ if (!mOfflineDevice)
+ rv = CreateOfflineDevice();
+ if (mOfflineDevice)
+ rv = mOfflineDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY) {
+ // If there is no memory device, there is no need to evict it...
+ if (mMemoryDevice) {
+ nsresult rv = mMemoryDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ return res;
+}
+
+
+nsresult
+nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
+ bool * result)
+{
+ if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
+
+ *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::DoomEntry(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener)
+{
+ CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
+ session, PromiseFlatCString(key).get()));
+ if (!gService || !gService->mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
+}
+
+
+bool
+nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
+{
+ if (gService->mEnableMemoryDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY)) {
+ return true;
+ }
+ if (gService->mEnableDiskDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK)) {
+ return true;
+ }
+ if (gService->mEnableOfflineDevice &&
+ storagePolicy == nsICache::STORE_OFFLINE) {
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return VisitEntriesInternal(visitor);
+}
+
+nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor *visitor)
+{
+ NS_ENSURE_ARG_POINTER(visitor);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
+
+ if (!(mEnableDiskDevice || mEnableMemoryDevice))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX record the fact that a visitation is in progress,
+ // XXX i.e. keep list of visitors in progress.
+
+ nsresult rv = NS_OK;
+ // If there is no memory device, there are then also no entries to visit...
+ if (mMemoryDevice) {
+ rv = mMemoryDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ rv = CreateDiskDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mDiskDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ rv = CreateOfflineDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mOfflineDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // XXX notify any shutdown process that visitation is complete for THIS visitor.
+ // XXX keep queue of visitors
+
+ return NS_OK;
+}
+
+void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
+ if (obsvc) {
+ obsvc->NotifyObservers(nullptr,
+ "network-clear-cache-stored-anywhere",
+ nullptr);
+ }
+}
+
+NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return EvictEntriesInternal(storagePolicy);
+}
+
+nsresult nsCacheService::EvictEntriesInternal(nsCacheStoragePolicy storagePolicy)
+{
+ if (storagePolicy == nsICache::STORE_ANYWHERE) {
+ // if not called on main thread, dispatch the notification to the main thread to notify observers
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this,
+ &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
+ NS_DispatchToMainThread(event);
+ } else {
+ // else you're already on main thread - notify observers
+ FireClearNetworkCacheStoredAnywhereNotification();
+ }
+ }
+ return EvictEntriesForClient(nullptr, storagePolicy);
+}
+
+NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
+{
+ NS_ENSURE_ARG_POINTER(aCacheIOTarget);
+
+ // Because mCacheIOThread can only be changed on the main thread, it can be
+ // read from the main thread without the lock. This is useful to prevent
+ // blocking the main thread on other cache operations.
+ if (!NS_IsMainThread()) {
+ Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
+ }
+
+ nsresult rv;
+ if (mCacheIOThread) {
+ NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
+ rv = NS_OK;
+ } else {
+ *aCacheIOTarget = nullptr;
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!NS_IsMainThread()) {
+ Unlock();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheService::GetLockHeldTime(double *aLockHeldTime)
+{
+ MutexAutoLock lock(mTimeStampLock);
+
+ if (mLockAcquiredTimeStamp.IsNull()) {
+ *aLockHeldTime = 0.0;
+ }
+ else {
+ *aLockHeldTime =
+ (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Internal Methods
+ */
+nsresult
+nsCacheService::CreateDiskDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mDiskDevice) return NS_OK;
+
+ mDiskDevice = new nsDiskCacheDevice;
+ if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set the preferences
+ mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
+ mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
+ mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
+
+ nsresult rv = mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+#if DEBUG
+ printf("###\n");
+ printf("### mDiskDevice->Init() failed (0x%.8x)\n",
+ static_cast<uint32_t>(rv));
+ printf("### - disabling disk cache for this session.\n");
+ printf("###\n");
+#endif
+ mEnableDiskDevice = false;
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+ return rv;
+ }
+
+ NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
+
+ // Disk device is usually created during the startup. Delay smart size
+ // calculation to avoid possible massive IO caused by eviction of entries
+ // in case the new smart size is smaller than current cache usage.
+ mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
+ 1000*60*3,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to post smart size timer");
+ mSmartSizeTimer = nullptr;
+ }
+ } else {
+ NS_WARNING("Can't create smart size timer");
+ }
+ // Ignore state of the timer and return success since the purpose of the
+ // method (create the disk-device) has been fulfilled
+
+ return NS_OK;
+}
+
+// Runnable sent from cache thread to main thread
+class nsDisableOldMaxSmartSizePrefEvent: public Runnable
+{
+public:
+ nsDisableOldMaxSmartSizePrefEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to disable old max smart size");
+ return rv;
+ }
+
+ // It is safe to call SetDiskSmartSize_Locked() without holding the lock
+ // when we are on main thread and nsCacheService is initialized.
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+
+ if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
+ rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set cache capacity pref");
+ }
+ }
+
+ return NS_OK;
+ }
+};
+
+void
+nsCacheService::MarkStartingFresh()
+{
+ if (!gService || !gService->mObserver->ShouldUseOldMaxSmartSize()) {
+ // Already using new max, nothing to do here
+ return;
+ }
+
+ gService->mObserver->SetUseNewMaxSmartSize(true);
+
+ // We always dispatch an event here because we don't want to deal with lock
+ // reentrance issues.
+ NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
+}
+
+nsresult
+nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
+{
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aDevice = mOfflineDevice);
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice)
+{
+ nsresult rv;
+
+ nsAutoString profilePath;
+ rv = aProfileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
+ rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*aDevice)->SetAutoShutdown();
+ mCustomOfflineDevices.Put(profilePath, *aDevice);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateOfflineDevice()
+{
+ CACHE_LOG_INFO(("Creating default offline device"));
+
+ if (mOfflineDevice) return NS_OK;
+ if (!nsCacheService::IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CreateCustomOfflineDevice(
+ mObserver->OfflineCacheParentDirectory(),
+ mObserver->OfflineCacheCapacity(),
+ &mOfflineDevice);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice)
+{
+ NS_ENSURE_ARG(aProfileDir);
+
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
+ nsAutoCString profilePath;
+ aProfileDir->GetNativePath(profilePath);
+ CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
+ profilePath.BeginReading(), aQuota));
+ }
+
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
+
+ *aDevice = new nsOfflineCacheDevice;
+
+ NS_ADDREF(*aDevice);
+
+ // set the preferences
+ (*aDevice)->SetCacheParentDirectory(aProfileDir);
+ (*aDevice)->SetCapacity(aQuota);
+
+ nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8x)\n", rv));
+ CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
+
+ NS_RELEASE(*aDevice);
+ }
+ return rv;
+}
+
+nsresult
+nsCacheService::CreateMemoryDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mMemoryDevice) return NS_OK;
+
+ mMemoryDevice = new nsMemoryCacheDevice;
+ if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set preference
+ int32_t capacity = mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
+ mMemoryDevice->SetCapacity(capacity);
+ mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
+
+ nsresult rv = mMemoryDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Initialization of Memory Cache failed.");
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult
+nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
+{
+ nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
+ if (!profileDir)
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoString profilePath;
+ nsresult rv = profileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCustomOfflineDevices.Remove(profilePath);
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateRequest(nsCacheSession * session,
+ const nsACString & clientKey,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsCacheRequest ** request)
+{
+ NS_ASSERTION(request, "CreateRequest: request is null");
+
+ nsAutoCString key(*session->ClientID());
+ key.Append(':');
+ key.Append(clientKey);
+
+ if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
+
+ // create request
+ *request = new nsCacheRequest(key, listener, accessRequested,
+ blockingMode, session);
+
+ if (!listener) return NS_OK; // we're sync, we're done.
+
+ // get the request's thread
+ (*request)->mThread = do_GetCurrentThread();
+
+ return NS_OK;
+}
+
+
+class nsCacheListenerEvent : public Runnable
+{
+public:
+ nsCacheListenerEvent(nsICacheListener *listener,
+ nsICacheEntryDescriptor *descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status)
+ : mListener(listener) // transfers reference
+ , mDescriptor(descriptor) // transfers reference (may be null)
+ , mAccessGranted(accessGranted)
+ , mStatus(status)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
+
+ NS_RELEASE(mListener);
+ NS_IF_RELEASE(mDescriptor);
+ return NS_OK;
+ }
+
+private:
+ // We explicitly leak mListener or mDescriptor if Run is not called
+ // because otherwise we cannot guarantee that they are destroyed on
+ // the right thread.
+
+ nsICacheListener *mListener;
+ nsICacheEntryDescriptor *mDescriptor;
+ nsCacheAccessMode mAccessGranted;
+ nsresult mStatus;
+};
+
+
+nsresult
+nsCacheService::NotifyListener(nsCacheRequest * request,
+ nsICacheEntryDescriptor * descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status)
+{
+ NS_ASSERTION(request->mThread, "no thread set in async request!");
+
+ // Swap ownership, and release listener on target thread...
+ nsICacheListener *listener = request->mListener;
+ request->mListener = nullptr;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsCacheListenerEvent(listener, descriptor,
+ accessGranted, status);
+ if (!ev) {
+ // Better to leak listener and descriptor if we fail because we don't
+ // want to destroy them inside the cache service lock or on potentially
+ // the wrong thread.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+
+nsresult
+nsCacheService::ProcessRequest(nsCacheRequest * request,
+ bool calledFromOpenCacheEntry,
+ nsICacheEntryDescriptor ** result)
+{
+ // !!! must be called with mLock held !!!
+ nsresult rv;
+ nsCacheEntry * entry = nullptr;
+ nsCacheEntry * doomedEntry = nullptr;
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+ if (result) *result = nullptr;
+
+ while(1) { // Activate entry loop
+ rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
+ if (NS_FAILED(rv)) break;
+
+ while(1) { // Request Access loop
+ NS_ASSERTION(entry, "no entry in Request Access loop!");
+ // entry->RequestAccess queues request on entry
+ rv = entry->RequestAccess(request, &accessGranted);
+ if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
+
+ if (request->IsBlocking()) {
+ if (request->mListener) {
+ // async exits - validate, doom, or close will resume
+ return rv;
+ }
+
+ // XXX this is probably wrong...
+ Unlock();
+ rv = request->WaitForValidation();
+ Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
+ }
+
+ PR_REMOVE_AND_INIT_LINK(request);
+ if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
+ // okay, we're ready to process this request, request access again
+ }
+ if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
+
+ if (entry->IsNotInUse()) {
+ // this request was the last one keeping it around, so get rid of it
+ DeactivateEntry(entry);
+ }
+ // loop back around to look for another entry
+ }
+
+ if (NS_SUCCEEDED(rv) && request->mProfileDir) {
+ // Custom cache directory has been demanded. Preset the cache device.
+ if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
+ // Failsafe check: this is implemented only for offline cache atm.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ RefPtr<nsOfflineCacheDevice> customCacheDevice;
+ rv = GetCustomOfflineDevice(request->mProfileDir, -1,
+ getter_AddRefs(customCacheDevice));
+ if (NS_SUCCEEDED(rv))
+ entry->SetCustomCacheDevice(customCacheDevice);
+ }
+ }
+
+ nsICacheEntryDescriptor *descriptor = nullptr;
+
+ if (NS_SUCCEEDED(rv))
+ rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
+
+ // If doomedEntry is set, ActivatEntry() doomed an existing entry and
+ // created a new one for that cache-key. However, any pending requests
+ // on the doomed entry were not processed and we need to do that here.
+ // This must be done after adding the created entry to list of active
+ // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
+ // (see bug ##561313). It is also important to do this after creating a
+ // descriptor for this request, or some other request may end up being
+ // executed first for the newly created entry.
+ // Finally, it is worth to emphasize that if doomedEntry is set,
+ // ActivateEntry() created a new entry for the request, which will be
+ // initialized by RequestAccess() and they both should have returned NS_OK.
+ if (doomedEntry) {
+ (void) ProcessPendingRequests(doomedEntry);
+ if (doomedEntry->IsNotInUse())
+ DeactivateEntry(doomedEntry);
+ doomedEntry = nullptr;
+ }
+
+ if (request->mListener) { // Asynchronous
+
+ if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
+ return rv; // skip notifying listener, just return rv to caller
+
+ // call listener to report error or descriptor
+ nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
+ rv = rv2; // trigger delete request
+ }
+ } else { // Synchronous
+ *result = descriptor;
+ }
+ return rv;
+}
+
+
+nsresult
+nsCacheService::OpenCacheEntry(nsCacheSession * session,
+ const nsACString & key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsICacheEntryDescriptor ** result)
+{
+ CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
+ session, PromiseFlatCString(key).get(), accessRequested,
+ blockingMode));
+ if (result)
+ *result = nullptr;
+
+ if (!gService || !gService->mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCacheRequest * request = nullptr;
+
+ nsresult rv = gService->CreateRequest(session,
+ key,
+ accessRequested,
+ blockingMode,
+ listener,
+ &request);
+ if (NS_FAILED(rv)) return rv;
+
+ CACHE_LOG_DEBUG(("Created request %p\n", request));
+
+ // Process the request on the background thread if we are on the main thread
+ // and the the request is asynchronous
+ if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
+ nsCOMPtr<nsIRunnable> ev =
+ new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+
+ // delete request if we didn't post the event
+ if (NS_FAILED(rv))
+ delete request;
+ }
+ else {
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
+ rv = gService->ProcessRequest(request, true, result);
+
+ // delete requests that have completed
+ if (!(listener && blockingMode &&
+ (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
+ delete request;
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsCacheService::ActivateEntry(nsCacheRequest * request,
+ nsCacheEntry ** result,
+ nsCacheEntry ** doomedEntry)
+{
+ CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
+ if (!mInitialized || mClearingEntries)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
+ if (result) *result = nullptr;
+ if (doomedEntry) *doomedEntry = nullptr;
+ if ((!request) || (!result) || (!doomedEntry))
+ return NS_ERROR_NULL_POINTER;
+
+ // check if the request can be satisfied
+ if (!mEnableMemoryDevice && !request->IsStreamBased())
+ return NS_ERROR_FAILURE;
+ if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
+ return NS_ERROR_FAILURE;
+
+ // search active entries (including those not bound to device)
+ nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
+ CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
+
+ if (!entry) {
+ // search cache devices for entry
+ bool collision = false;
+ entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
+ CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
+ request, entry));
+ // When there is a hashkey collision just refuse to cache it...
+ if (collision) return NS_ERROR_CACHE_IN_USE;
+
+ if (entry) entry->MarkInitialized();
+ } else {
+ NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
+ }
+
+ if (entry) {
+ ++mCacheHits;
+ entry->Fetched();
+ } else {
+ ++mCacheMisses;
+ }
+
+ if (entry &&
+ ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
+ ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
+ (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
+ request->WillDoomEntriesIfExpired()))))
+
+ {
+ // this is FORCE-WRITE request or the entry has expired
+ // we doom entry without processing pending requests, but store it in
+ // doomedEntry which causes pending requests to be processed below
+ rv = DoomEntry_Internal(entry, false);
+ *doomedEntry = entry;
+ if (NS_FAILED(rv)) {
+ // XXX what to do? Increment FailedDooms counter?
+ }
+ entry = nullptr;
+ }
+
+ if (!entry) {
+ if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
+ // this is a READ-ONLY request
+ rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
+ goto error;
+ }
+
+ entry = new nsCacheEntry(request->mKey,
+ request->IsStreamBased(),
+ request->StoragePolicy());
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (request->IsPrivate())
+ entry->MarkPrivate();
+
+ entry->Fetched();
+ ++mTotalEntries;
+
+ // XXX we could perform an early bind in some cases based on storage policy
+ }
+
+ if (!entry->IsActive()) {
+ rv = mActiveEntries.AddEntry(entry);
+ if (NS_FAILED(rv)) goto error;
+ CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
+ entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
+ }
+ *result = entry;
+ return NS_OK;
+
+ error:
+ *result = nullptr;
+ delete entry;
+ return rv;
+}
+
+
+nsCacheEntry *
+nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
+{
+ Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
+ nsCacheEntry * entry = nullptr;
+
+ CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
+
+ *collision = false;
+ if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
+ // If there is no memory device, then there is nothing to search...
+ if (mMemoryDevice) {
+ entry = mMemoryDevice->FindEntry(key, collision);
+ CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
+ "collision: %d\n", key->get(), entry, collision));
+ }
+ }
+
+ if (!entry &&
+ ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ nsresult rv = CreateDiskDevice();
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ entry = mDiskDevice->FindEntry(key, collision);
+ }
+ }
+
+ if (!entry && (policy == nsICache::STORE_OFFLINE ||
+ (policy == nsICache::STORE_ANYWHERE &&
+ gIOService->IsOffline()))) {
+
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ entry = mOfflineDevice->FindEntry(key, collision);
+ }
+ }
+
+ return entry;
+}
+
+
+nsCacheDevice *
+nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
+{
+ nsCacheDevice * device = entry->CacheDevice();
+ // return device if found, possibly null if the entry is doomed i.e prevent
+ // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
+ if (device || entry->IsDoomed()) return device;
+
+ int64_t predictedDataSize = entry->PredictedDataSize();
+ if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
+ // this is the default
+ if (!mDiskDevice) {
+ (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
+ }
+
+ if (mDiskDevice) {
+ // Bypass the cache if Content-Length says the entry will be too big
+ if (predictedDataSize != -1 &&
+ mDiskDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mDiskDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mDiskDevice;
+ }
+ }
+
+ // if we can't use mDiskDevice, try mMemoryDevice
+ if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
+ if (!mMemoryDevice) {
+ (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
+ }
+ if (mMemoryDevice) {
+ // Bypass the cache if Content-Length says entry will be too big
+ if (predictedDataSize != -1 &&
+ mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mMemoryDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mMemoryDevice;
+ }
+ }
+
+ if (!device && entry->IsStreamData() &&
+ entry->IsAllowedOffline() && mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
+ }
+
+ device = entry->CustomCacheDevice()
+ ? entry->CustomCacheDevice()
+ : mOfflineDevice;
+
+ if (device) {
+ entry->MarkBinding();
+ nsresult rv = device->BindEntry(entry);
+ entry->ClearBinding();
+ if (NS_FAILED(rv))
+ device = nullptr;
+ }
+ }
+
+ if (device)
+ entry->SetCacheDevice(device);
+ return device;
+}
+
+nsresult
+nsCacheService::DoomEntry(nsCacheEntry * entry)
+{
+ return gService->DoomEntry_Internal(entry, true);
+}
+
+
+nsresult
+nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
+ bool doProcessPendingRequests)
+{
+ if (entry->IsDoomed()) return NS_OK;
+
+ CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
+ nsresult rv = NS_OK;
+ entry->MarkDoomed();
+
+ NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
+ nsCacheDevice * device = entry->CacheDevice();
+ if (device) device->DoomEntry(entry);
+
+ if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
+ entry->MarkInactive();
+ }
+
+ // put on doom list to wait for descriptors to close
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
+ PR_APPEND_LINK(entry, &mDoomedEntries);
+
+ // handle pending requests only if we're supposed to
+ if (doProcessPendingRequests) {
+ // tell pending requests to get on with their lives...
+ rv = ProcessPendingRequests(entry);
+
+ // All requests have been removed, but there may still be open descriptors
+ if (entry->IsNotInUse()) {
+ DeactivateEntry(entry); // tell device to get rid of it
+ }
+ }
+ return rv;
+}
+
+
+void
+nsCacheService::OnProfileShutdown()
+{
+ if (!gService || !gService->mInitialized) {
+ // The cache service has been shut down, but someone is still holding
+ // a reference to it. Ignore this call.
+ return;
+ }
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->mClearingEntries = true;
+ gService->DoomActiveEntries(nullptr);
+ }
+
+ gService->CloseAllStreams();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->ClearDoomList();
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void) SyncWithCacheIOThread();
+
+ if (gService->mDiskDevice && gService->mEnableDiskDevice) {
+ gService->mDiskDevice->Shutdown();
+ }
+ gService->mEnableDiskDevice = false;
+
+ if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
+ gService->mOfflineDevice->Shutdown();
+ }
+ for (auto iter = gService->mCustomOfflineDevices.Iter();
+ !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ gService->mEnableOfflineDevice = false;
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictEntries(nullptr);
+ }
+
+ gService->mClearingEntries = false;
+}
+
+
+void
+nsCacheService::OnProfileChanged()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
+ gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
+
+ // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
+ nsresult rv = gService->mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
+ gService->mEnableDiskDevice = false;
+ // XXX delete mDiskDevice?
+ }
+ }
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
+ gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
+
+ // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
+ nsresult rv = gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
+ gService->mEnableOfflineDevice = false;
+ // XXX delete mOfflineDevice?
+ }
+ }
+
+ // If memoryDevice exists, reset its size to the new profile
+ if (gService->mMemoryDevice) {
+ if (gService->mEnableMemoryDevice) {
+ // make sure that capacity is reset to the right value
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ } else {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+void
+nsCacheService::SetDiskCacheEnabled(bool enabled)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
+ gService->mEnableDiskDevice = enabled;
+}
+
+
+void
+nsCacheService::SetDiskCacheCapacity(int32_t capacity)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+}
+
+void
+nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+void
+nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
+
+ if (gService->mMemoryDevice) {
+ gService->mMemoryDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+void
+nsCacheService::SetOfflineCacheEnabled(bool enabled)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
+ gService->mEnableOfflineDevice = enabled;
+}
+
+void
+nsCacheService::SetOfflineCacheCapacity(int32_t capacity)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+}
+
+
+void
+nsCacheService::SetMemoryCache()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
+
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mEnableMemoryDevice) {
+ if (gService->mMemoryDevice) {
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ // make sure that capacity is reset to the right value
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ }
+ } else {
+ if (gService->mMemoryDevice) {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+/******************************************************************************
+ * static methods for nsCacheEntryDescriptor
+ *****************************************************************************/
+void
+nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
+{
+ // ask entry to remove descriptor
+ nsCacheEntry * entry = descriptor->CacheEntry();
+ bool doomEntry;
+ bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
+
+ if (!entry->IsValid()) {
+ gService->ProcessPendingRequests(entry);
+ }
+
+ if (doomEntry) {
+ gService->DoomEntry_Internal(entry, true);
+ return;
+ }
+
+ if (!stillActive) {
+ gService->DeactivateEntry(entry);
+ }
+}
+
+
+nsresult
+nsCacheService::GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->GetFileForEntry(entry, result);
+}
+
+
+nsresult
+nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenInputStreamForEntry(entry, mode, offset, result);
+}
+
+nsresult
+nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenOutputStreamForEntry(entry, mode, offset, result);
+}
+
+
+nsresult
+nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OnDataSizeChange(entry, deltaSize);
+}
+
+void
+nsCacheService::LockAcquired()
+{
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp::Now();
+}
+
+void
+nsCacheService::LockReleased()
+{
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp();
+}
+
+void
+nsCacheService::Lock()
+{
+ gService->mLock.Lock();
+ gService->LockAcquired();
+}
+
+void
+nsCacheService::Lock(mozilla::Telemetry::ID mainThreadLockerID)
+{
+ mozilla::Telemetry::ID lockerID;
+ mozilla::Telemetry::ID generalID;
+
+ if (NS_IsMainThread()) {
+ lockerID = mainThreadLockerID;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
+ } else {
+ lockerID = mozilla::Telemetry::HistogramCount;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
+ }
+
+ TimeStamp start(TimeStamp::Now());
+
+ nsCacheService::Lock();
+
+ TimeStamp stop(TimeStamp::Now());
+
+ // Telemetry isn't thread safe on its own, but this is OK because we're
+ // protecting it with the cache lock.
+ if (lockerID != mozilla::Telemetry::HistogramCount) {
+ mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
+ }
+ mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
+}
+
+void
+nsCacheService::Unlock()
+{
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsTArray<nsISupports*> doomed;
+ doomed.SwapElements(gService->mDoomedObjects);
+
+ gService->LockReleased();
+ gService->mLock.Unlock();
+
+ for (uint32_t i = 0; i < doomed.Length(); ++i)
+ doomed[i]->Release();
+}
+
+void
+nsCacheService::ReleaseObject_Locked(nsISupports * obj,
+ nsIEventTarget * target)
+{
+ gService->mLock.AssertCurrentThreadOwns();
+
+ bool isCur;
+ if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
+ gService->mDoomedObjects.AppendElement(obj);
+ } else {
+ NS_ProxyRelease(target, dont_AddRef(obj));
+ }
+}
+
+
+nsresult
+nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
+{
+ entry->SetData(element);
+ entry->TouchData();
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::ValidateEntry(nsCacheEntry * entry)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ entry->MarkValid();
+ nsresult rv = gService->ProcessPendingRequests(entry);
+ NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
+ // XXX what else should be done?
+
+ return rv;
+}
+
+
+int32_t
+nsCacheService::CacheCompressionLevel()
+{
+ int32_t level = gService->mObserver->CacheCompressionLevel();
+ return level;
+}
+
+
+void
+nsCacheService::DeactivateEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
+ nsresult rv = NS_OK;
+ NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
+ nsCacheDevice * device = nullptr;
+
+ if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
+ if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
+
+ if (entry->IsDoomed()) {
+ // remove from Doomed list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ } else if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
+ entry));
+ entry->MarkInactive();
+
+ // bind entry if necessary to store meta-data
+ device = EnsureEntryHasDevice(entry);
+ if (!device) {
+ CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
+ "entry %p\n",
+ entry));
+ NS_WARNING("DeactivateEntry: unable to bind active entry\n");
+ return;
+ }
+ } else {
+ // if mInitialized == false,
+ // then we're shutting down and this state is okay.
+ NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
+ }
+
+ device = entry->CacheDevice();
+ if (device) {
+ rv = device->DeactivateEntry(entry);
+ if (NS_FAILED(rv)) {
+ // increment deactivate failure count
+ ++mDeactivateFailures;
+ }
+ } else {
+ // increment deactivating unbound entry statistic
+ ++mDeactivatedUnboundEntries;
+ delete entry; // because no one else will
+ }
+}
+
+
+nsresult
+nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
+{
+ nsresult rv = NS_OK;
+ nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+ nsCacheRequest * nextRequest;
+ bool newWriter = false;
+
+ CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
+ (entry->IsInitialized()?"" : "Un"),
+ (entry->IsDoomed()?"DOOMED" : ""),
+ (entry->IsValid()? "V":"Inv"), entry));
+
+ if (request == &entry->mRequestQ) return NS_OK; // no queued requests
+
+ if (!entry->IsDoomed() && entry->IsInvalid()) {
+ // 1st descriptor closed w/o MarkValid()
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
+
+#if DEBUG
+ // verify no ACCESS_WRITE requests(shouldn't have any of these)
+ while (request != &entry->mRequestQ) {
+ NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
+ "ACCESS_WRITE request should have been given a new entry");
+ request = (nsCacheRequest *)PR_NEXT_LINK(request);
+ }
+ request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+#endif
+ // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
+ while (request != &entry->mRequestQ) {
+ if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
+ newWriter = true;
+ CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
+ break;
+ }
+
+ request = (nsCacheRequest *)PR_NEXT_LINK(request);
+ }
+
+ if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
+ request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+
+ // XXX what should we do if there are only READ requests in queue?
+ // XXX serialize their accesses, give them only read access, but force them to check validate flag?
+ // XXX or do readers simply presume the entry is valid
+ // See fix for bug #467392 below
+ }
+
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+
+ while (request != &entry->mRequestQ) {
+ nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
+ CACHE_LOG_DEBUG((" %sync request %p for %p\n",
+ (request->mListener?"As":"S"), request, entry));
+
+ if (request->mListener) {
+
+ // Async request
+ PR_REMOVE_AND_INIT_LINK(request);
+
+ if (entry->IsDoomed()) {
+ rv = ProcessRequest(request, false, nullptr);
+ if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
+ rv = NS_OK;
+ else
+ delete request;
+
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+ } else if (entry->IsValid() || newWriter) {
+ rv = entry->RequestAccess(request, &accessGranted);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "if entry is valid, RequestAccess must succeed.");
+ // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
+
+ // entry->CreateDescriptor dequeues request, and queues descriptor
+ nsICacheEntryDescriptor *descriptor = nullptr;
+ rv = entry->CreateDescriptor(request,
+ accessGranted,
+ &descriptor);
+
+ // post call to listener to report error or descriptor
+ rv = NotifyListener(request, descriptor, accessGranted, rv);
+ delete request;
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+
+ } else {
+ // read-only request to an invalid entry - need to wait for
+ // the entry to become valid so we post an event to process
+ // the request again later (bug #467392)
+ nsCOMPtr<nsIRunnable> ev =
+ new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+ if (NS_FAILED(rv)) {
+ delete request; // avoid leak
+ }
+ }
+ } else {
+
+ // Synchronous request
+ request->WakeUp();
+ }
+ if (newWriter) break; // process remaining requests after validation
+ request = nextRequest;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsCacheService::IsDoomListEmpty()
+{
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+ return &mDoomedEntries == entry;
+}
+
+void
+nsCacheService::ClearDoomList()
+{
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+
+ entry->DetachDescriptors();
+ DeactivateEntry(entry);
+ entry = next;
+ }
+}
+
+void
+nsCacheService::DoomActiveEntries(DoomCheckFn check)
+{
+ AutoTArray<nsCacheEntry*, 8> array;
+
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ nsCacheEntry* entry =
+ static_cast<nsCacheEntryHashTableEntry*>(iter.Get())->cacheEntry;
+
+ if (check && !check(entry)) {
+ continue;
+ }
+
+ array.AppendElement(entry);
+
+ // entry is being removed from the active entry list
+ entry->MarkInactive();
+ iter.Remove();
+ }
+
+ uint32_t count = array.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ DoomEntry_Internal(array[i], true);
+ }
+}
+
+void
+nsCacheService::CloseAllStreams()
+{
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
+
+ nsTArray<nsCacheEntry*> entries;
+
+#if DEBUG
+ // make sure there is no active entry
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<nsCacheEntryHashTableEntry*>(iter.Get());
+ entries.AppendElement(entry->cacheEntry);
+ }
+ NS_ASSERTION(entries.IsEmpty(), "Bad state");
+#endif
+
+ // Get doomed entries
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ entries.AppendElement(entry);
+ entry = next;
+ }
+
+ // Iterate through all entries and collect input and output streams
+ for (size_t i = 0; i < entries.Length(); i++) {
+ entry = entries.ElementAt(i);
+
+ nsTArray<RefPtr<nsCacheEntryDescriptor> > descs;
+ entry->GetDescriptors(descs);
+
+ for (uint32_t j = 0 ; j < descs.Length() ; j++) {
+ if (descs[j]->mOutputWrapper)
+ outputs.AppendElement(descs[j]->mOutputWrapper);
+
+ for (size_t k = 0; k < descs[j]->mInputWrappers.Length(); k++)
+ inputs.AppendElement(descs[j]->mInputWrappers[k]);
+ }
+ }
+ }
+
+ uint32_t i;
+ for (i = 0 ; i < inputs.Length() ; i++)
+ inputs[i]->Close();
+
+ for (i = 0 ; i < outputs.Length() ; i++)
+ outputs[i]->Close();
+}
+
+
+bool
+nsCacheService::GetClearingEntries()
+{
+ AssertOwnsLock();
+ return gService->mClearingEntries;
+}
+
+// static
+void nsCacheService::GetCacheBaseDirectoty(nsIFile ** result)
+{
+ *result = nullptr;
+ if (!gService || !gService->mObserver)
+ return;
+
+ nsCOMPtr<nsIFile> directory =
+ gService->mObserver->DiskCacheParentDirectory();
+ if (!directory)
+ return;
+
+ directory->Clone(result);
+}
+
+// static
+void nsCacheService::GetDiskCacheDirectory(nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory)
+ return;
+
+ nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_FAILED(rv))
+ return;
+
+ directory.forget(result);
+}
+
+// static
+void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory)
+ return;
+
+ nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ directory.forget(result);
+}
+
+
+void
+nsCacheService::LogCacheStatistics()
+{
+ uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
+ ((double)(mCacheHits + mCacheMisses))) * 100);
+ CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
+ CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
+ CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
+ CACHE_LOG_INFO((" Cache Misses = %d\n", mCacheMisses));
+ CACHE_LOG_INFO((" Cache Hit %% = %d%%\n", hitPercentage));
+ CACHE_LOG_INFO((" Max Key Length = %d\n", mMaxKeyLength));
+ CACHE_LOG_INFO((" Max Meta Size = %d\n", mMaxMetaSize));
+ CACHE_LOG_INFO((" Max Data Size = %d\n", mMaxDataSize));
+ CACHE_LOG_INFO(("\n"));
+ CACHE_LOG_INFO((" Deactivate Failures = %d\n",
+ mDeactivateFailures));
+ CACHE_LOG_INFO((" Deactivated Unbound Entries = %d\n",
+ mDeactivatedUnboundEntries));
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
+
+ if (!gService) return NS_ERROR_NOT_AVAILABLE;
+
+ return gService->SetDiskSmartSize_Locked();
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize_Locked()
+{
+ nsresult rv;
+
+ if (mozilla::net::CacheObserver::UseNewCache()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mObserver->DiskCacheParentDirectory())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mDiskDevice)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mObserver->SmartSizeEnabled())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoString cachePath;
+ rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
+ mObserver->ShouldUseOldMaxSmartSize());
+ DispatchToCacheIOThread(event);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
+ nsIFile *aNewCacheDir,
+ const char *aCacheSubdir)
+{
+ bool same;
+ if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
+ return;
+
+ nsCOMPtr<nsIFile> aOldCacheSubdir;
+ aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
+
+ nsresult rv = aOldCacheSubdir->AppendNative(
+ nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv))
+ return;
+
+ bool exists;
+ if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
+ return;
+
+ nsCOMPtr<nsIFile> aNewCacheSubdir;
+ aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
+
+ rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv))
+ return;
+
+ nsAutoCString newPath;
+ rv = aNewCacheSubdir->GetNativePath(newPath);
+ if (NS_FAILED(rv))
+ return;
+
+ if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
+ // New cache directory does not exist, try to move the old one here
+ // rename needs an empty target directory
+
+ // Make sure the parent of the target sub-dir exists
+ rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
+ nsAutoCString oldPath;
+ rv = aOldCacheSubdir->GetNativePath(oldPath);
+ if (NS_FAILED(rv))
+ return;
+ if (rename(oldPath.get(), newPath.get()) == 0)
+ return;
+ }
+ }
+
+ // Delay delete by 1 minute to avoid IO thrash on startup.
+ nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
+}
+
+static bool
+IsEntryPrivate(nsCacheEntry* entry)
+{
+ return entry->IsPrivate();
+}
+
+void
+nsCacheService::LeavePrivateBrowsing()
+{
+ nsCacheServiceAutoLock lock;
+
+ gService->DoomActiveEntries(IsEntryPrivate);
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictPrivateEntries();
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
+
+NS_IMETHODIMP
+nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ size_t disk = 0;
+ if (mDiskDevice) {
+ nsCacheServiceAutoLock
+ lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
+ disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
+ }
+
+ size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/disk-cache", KIND_HEAP, UNITS_BYTES, disk,
+ "Memory used by the network disk cache.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/memory-cache", KIND_HEAP, UNITS_BYTES, memory,
+ "Memory used by the network memory cache.");
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheService.h b/netwerk/cache/nsCacheService.h
new file mode 100644
index 000000000..1751d4875
--- /dev/null
+++ b/netwerk/cache/nsCacheService.h
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheService_h_
+#define _nsCacheService_h_
+
+#include "nsICacheService.h"
+#include "nsCacheSession.h"
+#include "nsCacheDevice.h"
+#include "nsCacheEntry.h"
+#include "nsThreadUtils.h"
+#include "nsICacheListener.h"
+#include "nsIMemoryReporter.h"
+
+#include "prthread.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Telemetry.h"
+
+class nsCacheRequest;
+class nsCacheProfilePrefObserver;
+class nsDiskCacheDevice;
+class nsMemoryCacheDevice;
+class nsOfflineCacheDevice;
+class nsCacheServiceAutoLock;
+class nsITimer;
+class mozIStorageService;
+
+
+/******************************************************************************
+ * nsNotifyDoomListener
+ *****************************************************************************/
+
+class nsNotifyDoomListener : public mozilla::Runnable {
+public:
+ nsNotifyDoomListener(nsICacheListener *listener,
+ nsresult status)
+ : mListener(listener) // transfers reference
+ , mStatus(status)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mListener->OnCacheEntryDoomed(mStatus);
+ NS_RELEASE(mListener);
+ return NS_OK;
+ }
+
+private:
+ nsICacheListener *mListener;
+ nsresult mStatus;
+};
+
+/******************************************************************************
+ * nsCacheService
+ ******************************************************************************/
+
+class nsCacheService final : public nsICacheServiceInternal,
+ public nsIMemoryReporter
+{
+ virtual ~nsCacheService();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESERVICE
+ NS_DECL_NSICACHESERVICEINTERNAL
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsCacheService();
+
+ // Define a Create method to be used with a factory:
+ static nsresult
+ Create(nsISupports* outer, const nsIID& iid, void* *result);
+
+
+ /**
+ * Methods called by nsCacheSession
+ */
+ static nsresult OpenCacheEntry(nsCacheSession * session,
+ const nsACString & key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsICacheEntryDescriptor ** result);
+
+ static nsresult EvictEntriesForSession(nsCacheSession * session);
+
+ static nsresult IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
+ bool * result);
+
+ static nsresult DoomEntry(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener);
+
+ /**
+ * Methods called by nsCacheEntryDescriptor
+ */
+
+ static void CloseDescriptor(nsCacheEntryDescriptor * descriptor);
+
+ static nsresult GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result);
+
+ static nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result);
+
+ static nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result);
+
+ static nsresult OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize);
+
+ static nsresult SetCacheElement(nsCacheEntry * entry, nsISupports * element);
+
+ static nsresult ValidateEntry(nsCacheEntry * entry);
+
+ static int32_t CacheCompressionLevel();
+
+ static bool GetClearingEntries();
+
+ static void GetCacheBaseDirectoty(nsIFile ** result);
+ static void GetDiskCacheDirectory(nsIFile ** result);
+ static void GetAppCacheDirectory(nsIFile ** result);
+
+ /**
+ * Methods called by any cache classes
+ */
+
+ static
+ nsCacheService * GlobalInstance() { return gService; }
+
+ static nsresult DoomEntry(nsCacheEntry * entry);
+
+ static bool IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy policy);
+
+ /**
+ * Called by disk cache to notify us to use the new max smart size
+ */
+ static void MarkStartingFresh();
+
+ /**
+ * Methods called by nsApplicationCacheService
+ */
+
+ nsresult GetOfflineDevice(nsOfflineCacheDevice ** aDevice);
+
+ /**
+ * Creates an offline cache device that works over a specific profile directory.
+ * A tool to preload offline cache for profiles different from the current
+ * application's profile directory.
+ */
+ nsresult GetCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice);
+
+ // This method may be called to release an object while the cache service
+ // lock is being held. If a non-null target is specified and the target
+ // does not correspond to the current thread, then the release will be
+ // proxied to the specified target. Otherwise, the object will be added to
+ // the list of objects to be released when the cache service is unlocked.
+ static void ReleaseObject_Locked(nsISupports * object,
+ nsIEventTarget * target = nullptr);
+
+ static nsresult DispatchToCacheIOThread(nsIRunnable* event);
+
+ // Calling this method will block the calling thread until all pending
+ // events on the cache-io thread has finished. The calling thread must
+ // hold the cache-lock
+ static nsresult SyncWithCacheIOThread();
+
+
+ /**
+ * Methods called by nsCacheProfilePrefObserver
+ */
+ static void OnProfileShutdown();
+ static void OnProfileChanged();
+
+ static void SetDiskCacheEnabled(bool enabled);
+ // Sets the disk cache capacity (in kilobytes)
+ static void SetDiskCacheCapacity(int32_t capacity);
+ // Set max size for a disk-cache entry (in KB). -1 disables limit up to
+ // 1/8th of disk cache size
+ static void SetDiskCacheMaxEntrySize(int32_t maxSize);
+ // Set max size for a memory-cache entry (in kilobytes). -1 disables
+ // limit up to 90% of memory cache size
+ static void SetMemoryCacheMaxEntrySize(int32_t maxSize);
+
+ static void SetOfflineCacheEnabled(bool enabled);
+ // Sets the offline cache capacity (in kilobytes)
+ static void SetOfflineCacheCapacity(int32_t capacity);
+
+ static void SetMemoryCache();
+
+ static void SetCacheCompressionLevel(int32_t level);
+
+ // Starts smart cache size computation if disk device is available
+ static nsresult SetDiskSmartSize();
+
+ static void MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
+ nsIFile *aNewCacheDir,
+ const char *aCacheSubdir);
+
+ nsresult Init();
+ void Shutdown();
+
+ static bool IsInitialized()
+ {
+ if (!gService) {
+ return false;
+ }
+ return gService->mInitialized;
+ }
+
+ static void AssertOwnsLock()
+ { gService->mLock.AssertCurrentThreadOwns(); }
+
+ static void LeavePrivateBrowsing();
+ bool IsDoomListEmpty();
+
+ typedef bool (*DoomCheckFn)(nsCacheEntry* entry);
+
+ // Accessors to the disabled functionality
+ nsresult CreateSessionInternal(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result);
+ nsresult VisitEntriesInternal(nsICacheVisitor *visitor);
+ nsresult EvictEntriesInternal(nsCacheStoragePolicy storagePolicy);
+
+private:
+ friend class nsCacheServiceAutoLock;
+ friend class nsOfflineCacheDevice;
+ friend class nsProcessRequestEvent;
+ friend class nsSetSmartSizeEvent;
+ friend class nsBlockOnCacheThreadEvent;
+ friend class nsSetDiskSmartSizeCallback;
+ friend class nsDoomEvent;
+ friend class nsDisableOldMaxSmartSizePrefEvent;
+ friend class nsDiskCacheMap;
+ friend class nsAsyncDoomEvent;
+ friend class nsCacheEntryDescriptor;
+
+ /**
+ * Internal Methods
+ */
+
+ static void Lock();
+ static void Lock(::mozilla::Telemetry::ID mainThreadLockerID);
+ static void Unlock();
+ void LockAcquired();
+ void LockReleased();
+
+ nsresult CreateDiskDevice();
+ nsresult CreateOfflineDevice();
+ nsresult CreateCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice);
+ nsresult CreateMemoryDevice();
+
+ nsresult RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice);
+
+ nsresult CreateRequest(nsCacheSession * session,
+ const nsACString & clientKey,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsCacheRequest ** request);
+
+ nsresult DoomEntry_Internal(nsCacheEntry * entry,
+ bool doProcessPendingRequests);
+
+ nsresult EvictEntriesForClient(const char * clientID,
+ nsCacheStoragePolicy storagePolicy);
+
+ // Notifies request listener asynchronously on the request's thread, and
+ // releases the descriptor on the request's thread. If this method fails,
+ // the descriptor is not released.
+ nsresult NotifyListener(nsCacheRequest * request,
+ nsICacheEntryDescriptor * descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult error);
+
+ nsresult ActivateEntry(nsCacheRequest * request,
+ nsCacheEntry ** entry,
+ nsCacheEntry ** doomedEntry);
+
+ nsCacheDevice * EnsureEntryHasDevice(nsCacheEntry * entry);
+
+ nsCacheEntry * SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision);
+
+ void DeactivateEntry(nsCacheEntry * entry);
+
+ nsresult ProcessRequest(nsCacheRequest * request,
+ bool calledFromOpenCacheEntry,
+ nsICacheEntryDescriptor ** result);
+
+ nsresult ProcessPendingRequests(nsCacheEntry * entry);
+
+ void ClearDoomList(void);
+ void DoomActiveEntries(DoomCheckFn check);
+ void CloseAllStreams();
+ void FireClearNetworkCacheStoredAnywhereNotification();
+
+ void LogCacheStatistics();
+
+ nsresult SetDiskSmartSize_Locked();
+
+ /**
+ * Data Members
+ */
+
+ static nsCacheService * gService; // there can be only one...
+
+ nsCOMPtr<mozIStorageService> mStorageService;
+
+ nsCacheProfilePrefObserver * mObserver;
+
+ mozilla::Mutex mLock;
+ mozilla::CondVar mCondVar;
+ bool mNotified;
+
+ mozilla::Mutex mTimeStampLock;
+ mozilla::TimeStamp mLockAcquiredTimeStamp;
+
+ nsCOMPtr<nsIThread> mCacheIOThread;
+
+ nsTArray<nsISupports*> mDoomedObjects;
+ nsCOMPtr<nsITimer> mSmartSizeTimer;
+
+ bool mInitialized;
+ bool mClearingEntries;
+
+ bool mEnableMemoryDevice;
+ bool mEnableDiskDevice;
+ bool mEnableOfflineDevice;
+
+ nsMemoryCacheDevice * mMemoryDevice;
+ nsDiskCacheDevice * mDiskDevice;
+ nsOfflineCacheDevice * mOfflineDevice;
+
+ nsRefPtrHashtable<nsStringHashKey, nsOfflineCacheDevice> mCustomOfflineDevices;
+
+ nsCacheEntryHashTable mActiveEntries;
+ PRCList mDoomedEntries;
+
+ // stats
+
+ uint32_t mTotalEntries;
+ uint32_t mCacheHits;
+ uint32_t mCacheMisses;
+ uint32_t mMaxKeyLength;
+ uint32_t mMaxDataSize;
+ uint32_t mMaxMetaSize;
+
+ // Unexpected error totals
+ uint32_t mDeactivateFailures;
+ uint32_t mDeactivatedUnboundEntries;
+};
+
+/******************************************************************************
+ * nsCacheServiceAutoLock
+ ******************************************************************************/
+
+#define LOCK_TELEM(x) \
+ (::mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_##x)
+
+// Instantiate this class to acquire the cache service lock for a particular
+// execution scope.
+class nsCacheServiceAutoLock {
+public:
+ nsCacheServiceAutoLock() {
+ nsCacheService::Lock();
+ }
+ explicit nsCacheServiceAutoLock(mozilla::Telemetry::ID mainThreadLockerID) {
+ nsCacheService::Lock(mainThreadLockerID);
+ }
+ ~nsCacheServiceAutoLock() {
+ nsCacheService::Unlock();
+ }
+};
+
+#endif // _nsCacheService_h_
diff --git a/netwerk/cache/nsCacheSession.cpp b/netwerk/cache/nsCacheSession.cpp
new file mode 100644
index 000000000..d82203aec
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCacheSession.h"
+#include "nsCacheService.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+
+NS_IMPL_ISUPPORTS(nsCacheSession, nsICacheSession)
+
+nsCacheSession::nsCacheSession(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased)
+ : mClientID(clientID),
+ mInfo(0)
+{
+ SetStoragePolicy(storagePolicy);
+
+ if (streamBased) MarkStreamBased();
+ else SetStoragePolicy(nsICache::STORE_IN_MEMORY);
+
+ MarkPublic();
+
+ MarkDoomEntriesIfExpired();
+}
+
+nsCacheSession::~nsCacheSession()
+{
+ /* destructor code */
+ // notify service we are going away?
+}
+
+
+NS_IMETHODIMP nsCacheSession::GetDoomEntriesIfExpired(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = WillDoomEntriesIfExpired();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheSession::SetProfileDirectory(nsIFile *profileDir)
+{
+ if (StoragePolicy() != nsICache::STORE_OFFLINE && profileDir) {
+ // Profile directory override is currently implemented only for
+ // offline cache. This is an early failure to prevent the request
+ // being processed before it would fail later because of inability
+ // to assign a cache base dir.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mProfileDir = profileDir;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::GetProfileDirectory(nsIFile **profileDir)
+{
+ if (mProfileDir)
+ NS_ADDREF(*profileDir = mProfileDir);
+ else
+ *profileDir = nullptr;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheSession::SetDoomEntriesIfExpired(bool doomEntriesIfExpired)
+{
+ if (doomEntriesIfExpired) MarkDoomEntriesIfExpired();
+ else ClearDoomEntriesIfExpired();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheSession::OpenCacheEntry(const nsACString & key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheEntryDescriptor ** result)
+{
+ nsresult rv;
+
+ if (NS_IsMainThread())
+ rv = NS_ERROR_NOT_AVAILABLE;
+ else
+ rv = nsCacheService::OpenCacheEntry(this,
+ key,
+ accessRequested,
+ blockingMode,
+ nullptr, // no listener
+ result);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsCacheSession::AsyncOpenCacheEntry(const nsACString & key,
+ nsCacheAccessMode accessRequested,
+ nsICacheListener *listener,
+ bool noWait)
+{
+ nsresult rv;
+ rv = nsCacheService::OpenCacheEntry(this,
+ key,
+ accessRequested,
+ !noWait,
+ listener,
+ nullptr); // no result
+
+ if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheSession::EvictEntries()
+{
+ return nsCacheService::EvictEntriesForSession(this);
+}
+
+
+NS_IMETHODIMP nsCacheSession::IsStorageEnabled(bool *result)
+{
+
+ return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result);
+}
+
+NS_IMETHODIMP nsCacheSession::DoomEntry(const nsACString &key,
+ nsICacheListener *listener)
+{
+ return nsCacheService::DoomEntry(this, key, listener);
+}
+
+NS_IMETHODIMP nsCacheSession::GetIsPrivate(bool* aPrivate)
+{
+ *aPrivate = IsPrivate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::SetIsPrivate(bool aPrivate)
+{
+ if (aPrivate)
+ MarkPrivate();
+ else
+ MarkPublic();
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheSession.h b/netwerk/cache/nsCacheSession.h
new file mode 100644
index 000000000..04b031902
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheSession_h_
+#define _nsCacheSession_h_
+
+#include "nspr.h"
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsICacheSession.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+class nsCacheSession : public nsICacheSession
+{
+ virtual ~nsCacheSession();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHESESSION
+
+ nsCacheSession(const char * clientID, nsCacheStoragePolicy storagePolicy, bool streamBased);
+
+ nsCString * ClientID() { return &mClientID; }
+
+ enum SessionInfo {
+ eStoragePolicyMask = 0x000000FF,
+ eStreamBasedMask = 0x00000100,
+ eDoomEntriesIfExpiredMask = 0x00001000,
+ ePrivateMask = 0x00010000
+ };
+
+ void MarkStreamBased() { mInfo |= eStreamBasedMask; }
+ void ClearStreamBased() { mInfo &= ~eStreamBasedMask; }
+ bool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; }
+
+ void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; }
+ void ClearDoomEntriesIfExpired() { mInfo &= ~eDoomEntriesIfExpiredMask; }
+ bool WillDoomEntriesIfExpired() { return (0 != (mInfo & eDoomEntriesIfExpiredMask)); }
+
+ void MarkPrivate() { mInfo |= ePrivateMask; }
+ void MarkPublic() { mInfo &= ~ePrivateMask; }
+ bool IsPrivate() { return (mInfo & ePrivateMask) != 0; }
+ nsCacheStoragePolicy StoragePolicy()
+ {
+ return (nsCacheStoragePolicy)(mInfo & eStoragePolicyMask);
+ }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy)
+ {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mInfo &= ~eStoragePolicyMask; // clear storage policy bits
+ mInfo |= policy;
+ }
+
+ nsIFile* ProfileDir() { return mProfileDir; }
+
+private:
+ nsCString mClientID;
+ uint32_t mInfo;
+ nsCOMPtr<nsIFile> mProfileDir;
+};
+
+#endif // _nsCacheSession_h_
diff --git a/netwerk/cache/nsCacheUtils.cpp b/netwerk/cache/nsCacheUtils.cpp
new file mode 100644
index 000000000..7cb562274
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsCacheUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+class nsDestroyThreadEvent : public Runnable {
+public:
+ explicit nsDestroyThreadEvent(nsIThread *thread)
+ : mThread(thread)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+nsShutdownThread::nsShutdownThread(nsIThread *aThread)
+ : mMonitor("nsShutdownThread.mMonitor")
+ , mShuttingDown(false)
+ , mThread(aThread)
+{
+}
+
+nsShutdownThread::~nsShutdownThread()
+{
+}
+
+nsresult
+nsShutdownThread::Shutdown(nsIThread *aThread)
+{
+ nsresult rv;
+ RefPtr<nsDestroyThreadEvent> ev = new nsDestroyThreadEvent(aThread);
+ rv = NS_DispatchToMainThread(ev);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Dispatching event in nsShutdownThread::Shutdown failed!");
+ }
+ return rv;
+}
+
+nsresult
+nsShutdownThread::BlockingShutdown(nsIThread *aThread)
+{
+ nsresult rv;
+
+ RefPtr<nsShutdownThread> st = new nsShutdownThread(aThread);
+ nsCOMPtr<nsIThread> workerThread;
+
+ rv = NS_NewNamedThread("thread shutdown", getter_AddRefs(workerThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create nsShutDownThread worker thread!");
+ return rv;
+ }
+
+ {
+ MonitorAutoLock mon(st->mMonitor);
+ rv = workerThread->Dispatch(st, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Dispatching event in nsShutdownThread::BlockingShutdown failed!");
+ } else {
+ st->mShuttingDown = true;
+ while (st->mShuttingDown) {
+ mon.Wait();
+ }
+ }
+ }
+
+ return Shutdown(workerThread);
+}
+
+NS_IMETHODIMP
+nsShutdownThread::Run()
+{
+ MonitorAutoLock mon(mMonitor);
+ mThread->Shutdown();
+ mShuttingDown = false;
+ mon.Notify();
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheUtils.h b/netwerk/cache/nsCacheUtils.h
new file mode 100644
index 000000000..65ae8f82b
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheUtils_h_
+#define _nsCacheUtils_h_
+
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Monitor.h"
+
+class nsIThread;
+
+/**
+ * A class with utility methods for shutting down nsIThreads easily.
+ */
+class nsShutdownThread : public mozilla::Runnable {
+public:
+ explicit nsShutdownThread(nsIThread *aThread);
+ ~nsShutdownThread();
+
+ NS_IMETHOD Run();
+
+/**
+ * Shutdown ensures that aThread->Shutdown() is called on a main thread
+ */
+ static nsresult Shutdown(nsIThread *aThread);
+
+/**
+ * BlockingShutdown ensures that by the time it returns, aThread->Shutdown() has
+ * been called and no pending events have been processed on the current thread.
+ */
+ static nsresult BlockingShutdown(nsIThread *aThread);
+
+private:
+ mozilla::Monitor mMonitor;
+ bool mShuttingDown;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+#endif
diff --git a/netwerk/cache/nsDeleteDir.cpp b/netwerk/cache/nsDeleteDir.cpp
new file mode 100644
index 000000000..1f3f3934e
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.cpp
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeleteDir.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "mozilla/Telemetry.h"
+#include "nsITimer.h"
+#include "nsISimpleEnumerator.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsCacheUtils.h"
+#include "prtime.h"
+#include <time.h>
+
+using namespace mozilla;
+
+class nsBlockOnBackgroundThreadEvent : public Runnable {
+public:
+ nsBlockOnBackgroundThreadEvent() {}
+ NS_IMETHOD Run() override
+ {
+ MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
+ nsDeleteDir::gInstance->mNotified = true;
+ nsDeleteDir::gInstance->mCondVar.Notify();
+ return NS_OK;
+ }
+};
+
+
+nsDeleteDir * nsDeleteDir::gInstance = nullptr;
+
+nsDeleteDir::nsDeleteDir()
+ : mLock("nsDeleteDir.mLock"),
+ mCondVar(mLock, "nsDeleteDir.mCondVar"),
+ mNotified(false),
+ mShutdownPending(false),
+ mStopDeleting(false)
+{
+ NS_ASSERTION(gInstance==nullptr, "multiple nsCacheService instances!");
+}
+
+nsDeleteDir::~nsDeleteDir()
+{
+ gInstance = nullptr;
+}
+
+nsresult
+nsDeleteDir::Init()
+{
+ if (gInstance)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ gInstance = new nsDeleteDir();
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::Shutdown(bool finishDeleting)
+{
+ if (!gInstance)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMArray<nsIFile> dirsToRemove;
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock lock(gInstance->mLock);
+ NS_ASSERTION(!gInstance->mShutdownPending,
+ "Unexpected state in nsDeleteDir::Shutdown()");
+ gInstance->mShutdownPending = true;
+
+ if (!finishDeleting)
+ gInstance->mStopDeleting = true;
+
+ // remove all pending timers
+ for (int32_t i = gInstance->mTimers.Count(); i > 0; i--) {
+ nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
+ gInstance->mTimers.RemoveObjectAt(i-1);
+
+ nsCOMArray<nsIFile> *arg;
+ timer->GetClosure((reinterpret_cast<void**>(&arg)));
+ timer->Cancel();
+
+ if (finishDeleting)
+ dirsToRemove.AppendObjects(*arg);
+
+ // delete argument passed to the timer
+ delete arg;
+ }
+
+ thread.swap(gInstance->mThread);
+ if (thread) {
+ // dispatch event and wait for it to run and notify us, so we know thread
+ // has completed all work and can be shutdown
+ nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
+ nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ gInstance->mNotified = false;
+ while (!gInstance->mNotified) {
+ gInstance->mCondVar.Wait();
+ }
+ nsShutdownThread::BlockingShutdown(thread);
+ }
+ }
+
+ delete gInstance;
+
+ for (int32_t i = 0; i < dirsToRemove.Count(); i++)
+ dirsToRemove[i]->Remove(true);
+
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::InitThread()
+{
+ if (mThread)
+ return NS_OK;
+
+ nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create background thread");
+ return rv;
+ }
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
+ if (p) {
+ p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+ return NS_OK;
+}
+
+void
+nsDeleteDir::DestroyThread()
+{
+ if (!mThread)
+ return;
+
+ if (mTimers.Count())
+ // more work to do, so don't delete thread.
+ return;
+
+ nsShutdownThread::Shutdown(mThread);
+ mThread = nullptr;
+}
+
+void
+nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
+{
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
+ {
+ MutexAutoLock lock(gInstance->mLock);
+
+ int32_t idx = gInstance->mTimers.IndexOf(aTimer);
+ if (idx == -1) {
+ // Timer was canceled and removed during shutdown.
+ return;
+ }
+
+ gInstance->mTimers.RemoveObjectAt(idx);
+ }
+
+ nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+ dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
+
+ bool shuttingDown = false;
+
+ // Intentional extra braces to control variable sope.
+ {
+ // Low IO priority can only be set when running in the context of the
+ // current thread. So this shouldn't be moved to where we set the priority
+ // of the Cache deleter thread using the nsThread's NSPR priority constants.
+ nsAutoLowPriorityIO autoLowPriority;
+ for (int32_t i = 0; i < dirList->Count() && !shuttingDown; i++) {
+ gInstance->RemoveDir((*dirList)[i], &shuttingDown);
+ }
+ }
+
+ {
+ MutexAutoLock lock(gInstance->mLock);
+ gInstance->DestroyThread();
+ }
+}
+
+nsresult
+nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, uint32_t delay)
+{
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
+
+ if (!gInstance)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> trash, dir;
+
+ // Need to make a clone of this since we don't want to modify the input
+ // file object.
+ rv = dirIn->Clone(getter_AddRefs(dir));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (moveToTrash) {
+ rv = GetTrashDir(dir, &trash);
+ if (NS_FAILED(rv))
+ return rv;
+ nsAutoCString origLeaf;
+ rv = trash->GetNativeLeafName(origLeaf);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Append random number to the trash directory and check if it exists.
+ srand(static_cast<unsigned>(PR_Now()));
+ nsAutoCString leaf;
+ for (int32_t i = 0; i < 10; i++) {
+ leaf = origLeaf;
+ leaf.AppendInt(rand());
+ rv = trash->SetNativeLeafName(leaf);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool exists;
+ if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ leaf.Truncate();
+ }
+
+ // Fail if we didn't find unused trash directory within the limit
+ if (!leaf.Length())
+ return NS_ERROR_FAILURE;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> parent;
+ rv = trash->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = dir->MoveToNative(parent, leaf);
+#else
+ // Important: must rename directory w/o changing parent directory: else on
+ // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
+ // tree: was hanging GUI for *minutes* on large cache dirs.
+ rv = dir->MoveToNative(nullptr, leaf);
+#endif
+ if (NS_FAILED(rv))
+ return rv;
+ } else {
+ // we want to pass a clone of the original off to the worker thread.
+ trash.swap(dir);
+ }
+
+ nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
+ arg->AppendObject(trash);
+
+ rv = gInstance->PostTimer(arg, delay);
+ if (NS_FAILED(rv))
+ return rv;
+
+ arg.forget();
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
+{
+ nsresult rv;
+#if defined(MOZ_WIDGET_ANDROID)
+ // Try to use the app cache folder for cache trash on Android
+ char* cachePath = getenv("CACHE_DIRECTORY");
+ if (cachePath) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
+ true, getter_AddRefs(*result));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Add a sub folder with the cache folder name
+ nsAutoCString leaf;
+ rv = target->GetNativeLeafName(leaf);
+ (*result)->AppendNative(leaf);
+ } else
+#endif
+ {
+ rv = target->Clone(getter_AddRefs(*result));
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString leaf;
+ rv = (*result)->GetNativeLeafName(leaf);
+ if (NS_FAILED(rv))
+ return rv;
+ leaf.AppendLiteral(".Trash");
+
+ return (*result)->SetNativeLeafName(leaf);
+}
+
+nsresult
+nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
+{
+ if (!gInstance)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> trash;
+ rv = GetTrashDir(cacheDir, &trash);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoString trashName;
+ rv = trash->GetLeafName(trashName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFile> parent;
+#if defined(MOZ_WIDGET_ANDROID)
+ rv = trash->GetParent(getter_AddRefs(parent));
+#else
+ rv = cacheDir->GetParent(getter_AddRefs(parent));
+#endif
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+ if (!file)
+ continue;
+
+ nsAutoString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_FAILED(rv))
+ continue;
+
+ // match all names that begin with the trash name (i.e. "Cache.Trash")
+ if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
+ if (!dirList)
+ dirList = new nsCOMArray<nsIFile>;
+ dirList->AppendObject(file);
+ }
+ }
+
+ if (dirList) {
+ rv = gInstance->PostTimer(dirList, 90000);
+ if (NS_FAILED(rv))
+ return rv;
+
+ dirList.forget();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::PostTimer(void *arg, uint32_t delay)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+
+ MutexAutoLock lock(mLock);
+
+ rv = InitThread();
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = timer->SetTarget(mThread);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mTimers.AppendObject(timer);
+ return NS_OK;
+}
+
+nsresult
+nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
+{
+ nsresult rv;
+ bool isLink;
+
+ rv = file->IsSymlink(&isLink);
+ if (NS_FAILED(rv) || isLink)
+ return NS_ERROR_UNEXPECTED;
+
+ bool isDir;
+ rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (isDir) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ rv = file->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool more;
+ nsCOMPtr<nsISupports> elem;
+ while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+ rv = iter->GetNext(getter_AddRefs(elem));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
+ if (!file2) {
+ NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+ continue;
+ }
+
+ RemoveDir(file2, stopDeleting);
+ // No check for errors to remove as much as possible
+
+ if (*stopDeleting)
+ return NS_OK;
+ }
+ }
+
+ file->Remove(false);
+ // No check for errors to remove as much as possible
+
+ MutexAutoLock lock(mLock);
+ if (mStopDeleting)
+ *stopDeleting = true;
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDeleteDir.h b/netwerk/cache/nsDeleteDir.h
new file mode 100644
index 000000000..6426efd26
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeleteDir_h__
+#define nsDeleteDir_h__
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+
+class nsIFile;
+class nsIThread;
+class nsITimer;
+
+
+class nsDeleteDir {
+public:
+ nsDeleteDir();
+ ~nsDeleteDir();
+
+ static nsresult Init();
+ static nsresult Shutdown(bool finishDeleting);
+
+ /**
+ * This routine attempts to delete a directory that may contain some files
+ * that are still in use. This latter point is only an issue on Windows and
+ * a few other systems.
+ *
+ * If the moveToTrash parameter is true we first rename the given directory
+ * "foo.Trash123" (where "foo" is the original directory name, and "123" is
+ * a random number, in order to support multiple concurrent deletes). The
+ * directory is then deleted, file-by-file, on a background thread.
+ *
+ * If the moveToTrash parameter is false, then the given directory is deleted
+ * directly.
+ *
+ * If 'delay' is non-zero, the directory will not be deleted until the
+ * specified number of milliseconds have passed. (The directory is still
+ * renamed immediately if 'moveToTrash' is passed, so upon return it is safe
+ * to create a directory with the same name).
+ */
+ static nsresult DeleteDir(nsIFile *dir, bool moveToTrash, uint32_t delay = 0);
+
+ /**
+ * Returns the trash directory corresponding to the given directory.
+ */
+ static nsresult GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result);
+
+ /**
+ * Remove all trashes left from previous run. This function does nothing when
+ * called second and more times.
+ */
+ static nsresult RemoveOldTrashes(nsIFile *cacheDir);
+
+ static void TimerCallback(nsITimer *aTimer, void *arg);
+
+private:
+ friend class nsBlockOnBackgroundThreadEvent;
+ friend class nsDestroyThreadEvent;
+
+ nsresult InitThread();
+ void DestroyThread();
+ nsresult PostTimer(void *arg, uint32_t delay);
+ nsresult RemoveDir(nsIFile *file, bool *stopDeleting);
+
+ static nsDeleteDir * gInstance;
+ mozilla::Mutex mLock;
+ mozilla::CondVar mCondVar;
+ bool mNotified;
+ nsCOMArray<nsITimer> mTimers;
+ nsCOMPtr<nsIThread> mThread;
+ bool mShutdownPending;
+ bool mStopDeleting;
+};
+
+#endif // nsDeleteDir_h__
diff --git a/netwerk/cache/nsDiskCache.h b/netwerk/cache/nsDiskCache.h
new file mode 100644
index 000000000..cc91553fa
--- /dev/null
+++ b/netwerk/cache/nsDiskCache.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsDiskCache_h_
+#define _nsDiskCache_h_
+
+#include "nsCacheEntry.h"
+
+#ifdef XP_WIN
+#include <winsock.h> // for htonl/ntohl
+#endif
+
+
+class nsDiskCache {
+public:
+ enum {
+ kCurrentVersion = 0x00010013 // format = 16 bits major version/16 bits minor version
+ };
+
+ enum { kData, kMetaData };
+
+ // Stores the reason why the cache is corrupt.
+ // Note: I'm only listing the enum values explicitly for easy mapping when
+ // looking at telemetry data.
+ enum CorruptCacheInfo {
+ kNotCorrupt = 0,
+ kInvalidArgPointer = 1,
+ kUnexpectedError = 2,
+ kOpenCacheMapError = 3,
+ kBlockFilesShouldNotExist = 4,
+ kOutOfMemory = 5,
+ kCreateCacheSubdirectories = 6,
+ kBlockFilesShouldExist = 7,
+ kHeaderSizeNotRead = 8,
+ kHeaderIsDirty = 9,
+ kVersionMismatch = 10,
+ kRecordsIncomplete = 11,
+ kHeaderIncomplete = 12,
+ kNotEnoughToRead = 13,
+ kEntryCountIncorrect = 14,
+ kCouldNotGetBlockFileForIndex = 15,
+ kCouldNotCreateBlockFile = 16,
+ kBlockFileSizeError = 17,
+ kBlockFileBitMapWriteError = 18,
+ kBlockFileSizeLessThanBitMap = 19,
+ kBlockFileBitMapReadError = 20,
+ kBlockFileEstimatedSizeError = 21,
+ kFlushHeaderError = 22,
+ kCacheCleanFilePathError = 23,
+ kCacheCleanOpenFileError = 24,
+ kCacheCleanTimerError = 25
+ };
+
+ // Parameter initval initializes internal state of hash function. Hash values are different
+ // for the same text when different initval is used. It can be any random number.
+ //
+ // It can be used for generating 64-bit hash value:
+ // (uint64_t(Hash(key, initval1)) << 32) | Hash(key, initval2)
+ //
+ // It can be also used to hash multiple strings:
+ // h = Hash(string1, 0);
+ // h = Hash(string2, h);
+ // ...
+ static PLDHashNumber Hash(const char* key, PLDHashNumber initval=0);
+ static nsresult Truncate(PRFileDesc * fd, uint32_t newEOF);
+};
+
+#endif // _nsDiskCache_h_
diff --git a/netwerk/cache/nsDiskCacheBinding.cpp b/netwerk/cache/nsDiskCacheBinding.cpp
new file mode 100644
index 000000000..cdc2606e6
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBinding.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCache.h"
+#include <limits.h>
+
+#include "nscore.h"
+#include "nsDiskCacheBinding.h"
+#include "nsCacheService.h"
+
+using namespace mozilla;
+
+/******************************************************************************
+ * static hash table callback functions
+ *
+ *****************************************************************************/
+struct HashTableEntry : PLDHashEntryHdr {
+ nsDiskCacheBinding * mBinding;
+};
+
+
+static PLDHashNumber
+HashKey(const void *key)
+{
+ return (PLDHashNumber) NS_PTR_TO_INT32(key);
+}
+
+
+static bool
+MatchEntry(const PLDHashEntryHdr * header,
+ const void * key)
+{
+ HashTableEntry * hashEntry = (HashTableEntry *) header;
+ return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key));
+}
+
+static void
+MoveEntry(PLDHashTable * /* table */,
+ const PLDHashEntryHdr * src,
+ PLDHashEntryHdr * dst)
+{
+ ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding;
+}
+
+
+static void
+ClearEntry(PLDHashTable * /* table */,
+ PLDHashEntryHdr * header)
+{
+ ((HashTableEntry *)header)->mBinding = nullptr;
+}
+
+
+/******************************************************************************
+ * Utility Functions
+ *****************************************************************************/
+nsDiskCacheBinding *
+GetCacheEntryBinding(nsCacheEntry * entry)
+{
+ return (nsDiskCacheBinding *) entry->Data();
+}
+
+
+/******************************************************************************
+ * nsDiskCacheBinding
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS0(nsDiskCacheBinding)
+
+nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record)
+ : mCacheEntry(entry)
+ , mStreamIO(nullptr)
+ , mDeactivateEvent(nullptr)
+{
+ NS_ASSERTION(record->ValidRecord(), "bad record");
+ PR_INIT_CLIST(this);
+ mRecord = *record;
+ mDoomed = entry->IsDoomed();
+ mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files
+}
+
+nsDiskCacheBinding::~nsDiskCacheBinding()
+{
+ // Grab the cache lock since the binding is stored in nsCacheEntry::mData
+ // and it is released using nsCacheService::ReleaseObject_Locked() which
+ // releases the object outside the cache lock.
+ nsCacheServiceAutoLock lock;
+
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list");
+ if (!PR_CLIST_IS_EMPTY(this))
+ PR_REMOVE_LINK(this); // XXX why are we still on a list?
+
+ // sever streamIO/binding link
+ if (mStreamIO) {
+ if (NS_FAILED(mStreamIO->ClearBinding()))
+ nsCacheService::DoomEntry(mCacheEntry);
+ NS_RELEASE(mStreamIO);
+ }
+}
+
+nsresult
+nsDiskCacheBinding::EnsureStreamIO()
+{
+ if (!mStreamIO) {
+ mStreamIO = new nsDiskCacheStreamIO(this);
+ if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(mStreamIO);
+ }
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheBindery
+ *
+ * Keeps track of bound disk cache entries to detect for collisions.
+ *
+ *****************************************************************************/
+
+const PLDHashTableOps nsDiskCacheBindery::ops =
+{
+ HashKey,
+ MatchEntry,
+ MoveEntry,
+ ClearEntry
+};
+
+
+nsDiskCacheBindery::nsDiskCacheBindery()
+ : table(&ops, sizeof(HashTableEntry), kInitialTableLength)
+ , initialized(false)
+{
+}
+
+
+nsDiskCacheBindery::~nsDiskCacheBindery()
+{
+ Reset();
+}
+
+
+void
+nsDiskCacheBindery::Init()
+{
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = true;
+}
+
+void
+nsDiskCacheBindery::Reset()
+{
+ if (initialized) {
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = false;
+ }
+}
+
+
+nsDiskCacheBinding *
+nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry,
+ nsDiskCacheRecord * record)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ nsCOMPtr<nsISupports> data = entry->Data();
+ if (data) {
+ NS_ERROR("cache entry already has bind data");
+ return nullptr;
+ }
+
+ nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record);
+ if (!binding) return nullptr;
+
+ // give ownership of the binding to the entry
+ entry->SetData(binding);
+
+ // add binding to collision detection system
+ nsresult rv = AddBinding(binding);
+ if (NS_FAILED(rv)) {
+ entry->SetData(nullptr);
+ return nullptr;
+ }
+
+ return binding;
+}
+
+
+/**
+ * FindActiveEntry : to find active colliding entry so we can doom it
+ */
+nsDiskCacheBinding *
+nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ // find hash entry for key
+ auto hashEntry = static_cast<HashTableEntry*>
+ (table.Search((void*)(uintptr_t)hashNumber));
+ if (!hashEntry) return nullptr;
+
+ // walk list looking for active entry
+ NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding");
+ nsDiskCacheBinding * binding = hashEntry->mBinding;
+ while (binding->mCacheEntry->IsDoomed()) {
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ if (binding == hashEntry->mBinding) return nullptr;
+ }
+ return binding;
+}
+
+
+/**
+ * AddBinding
+ *
+ * Called from FindEntry() if we read an entry off of disk
+ * - it may already have a generation number
+ * - a generation number conflict is an error
+ *
+ * Called from BindEntry()
+ * - a generation number needs to be assigned
+ */
+nsresult
+nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding)
+{
+ NS_ENSURE_ARG_POINTER(binding);
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+
+ // find hash entry for key
+ auto hashEntry = static_cast<HashTableEntry*>
+ (table.Add((void*)(uintptr_t)binding->mRecord.HashNumber(), fallible));
+ if (!hashEntry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (hashEntry->mBinding == nullptr) {
+ hashEntry->mBinding = binding;
+ if (binding->mGeneration == 0)
+ binding->mGeneration = 1; // if generation uninitialized, set it to 1
+
+ return NS_OK;
+ }
+
+
+ // insert binding in generation order
+ nsDiskCacheBinding * p = hashEntry->mBinding;
+ bool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation?
+ if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized
+ while (1) {
+
+ if (binding->mGeneration < p->mGeneration) {
+ // here we are
+ PR_INSERT_BEFORE(binding, p);
+ if (hashEntry->mBinding == p)
+ hashEntry->mBinding = binding;
+ break;
+ }
+
+ if (binding->mGeneration == p->mGeneration) {
+ if (calcGeneration) ++binding->mGeneration; // try the next generation
+ else {
+ NS_ERROR("### disk cache: generations collide!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ p = (nsDiskCacheBinding *)PR_NEXT_LINK(p);
+ if (p == hashEntry->mBinding) {
+ // end of line: insert here or die
+ p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation
+ if (p->mGeneration == 255) {
+ NS_WARNING("### disk cache: generation capacity at full");
+ return NS_ERROR_UNEXPECTED;
+ }
+ PR_INSERT_BEFORE(binding, hashEntry->mBinding);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * RemoveBinding : remove binding from collision detection on deactivation
+ */
+void
+nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return;
+
+ void* key = (void *)(uintptr_t)binding->mRecord.HashNumber();
+ auto hashEntry =
+ static_cast<HashTableEntry*>(table.Search((void*)(uintptr_t) key));
+ if (!hashEntry) {
+ NS_WARNING("### disk cache: binding not in hashtable!");
+ return;
+ }
+
+ if (binding == hashEntry->mBinding) {
+ if (PR_CLIST_IS_EMPTY(binding)) {
+ // remove this hash entry
+ table.Remove((void*)(uintptr_t) binding->mRecord.HashNumber());
+ return;
+
+ } else {
+ // promote next binding to head, and unlink this binding
+ hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ }
+ }
+ PR_REMOVE_AND_INIT_LINK(binding);
+}
+
+/**
+ * ActiveBindings: return true if any bindings have open descriptors.
+ */
+bool
+nsDiskCacheBindery::ActiveBindings()
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return false;
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<HashTableEntry*>(iter.Get());
+ nsDiskCacheBinding* binding = entry->mBinding;
+ nsDiskCacheBinding* head = binding;
+ do {
+ if (binding->IsActive()) {
+ return true;
+ }
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ } while (binding != head);
+ }
+
+ return false;
+}
+
+/**
+ * SizeOfExcludingThis: return the amount of heap memory (bytes) being used by
+ * the bindery.
+ */
+size_t
+nsDiskCacheBindery::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
+ if (!initialized) return 0;
+
+ size_t size = 0;
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<HashTableEntry*>(iter.Get());
+ nsDiskCacheBinding* binding = entry->mBinding;
+
+ nsDiskCacheBinding* head = binding;
+ do {
+ size += aMallocSizeOf(binding);
+ if (binding->mStreamIO) {
+ size += binding->mStreamIO->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // No good way to get at mDeactivateEvent internals for proper
+ // size, so we use this as an estimate.
+ if (binding->mDeactivateEvent) {
+ size += aMallocSizeOf(binding->mDeactivateEvent);
+ }
+ binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
+ } while (binding != head);
+ }
+
+ return size;
+}
diff --git a/netwerk/cache/nsDiskCacheBinding.h b/netwerk/cache/nsDiskCacheBinding.h
new file mode 100644
index 000000000..63c2094ce
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBinding.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsDiskCacheBinding_h_
+#define _nsDiskCacheBinding_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nspr.h"
+#include "PLDHashTable.h"
+
+#include "nsISupports.h"
+#include "nsCacheEntry.h"
+
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheStreams.h"
+
+
+/******************************************************************************
+ * nsDiskCacheBinding
+ *
+ * Created for disk cache specific data and stored in nsCacheEntry.mData as
+ * an nsISupports. Also stored in nsDiskCacheHashTable, with collisions
+ * linked by the PRCList.
+ *
+ *****************************************************************************/
+
+class nsDiskCacheDeviceDeactivateEntryEvent;
+
+class nsDiskCacheBinding : public nsISupports, public PRCList {
+ virtual ~nsDiskCacheBinding();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record);
+
+ nsresult EnsureStreamIO();
+ bool IsActive() { return mCacheEntry != nullptr;}
+
+// XXX make friends
+public:
+ nsCacheEntry* mCacheEntry; // back pointer to parent nsCacheEntry
+ nsDiskCacheRecord mRecord;
+ nsDiskCacheStreamIO* mStreamIO; // strong reference
+ bool mDoomed; // record is not stored in cache map
+ uint8_t mGeneration; // possibly just reservation
+
+ // If set, points to a pending event which will deactivate |mCacheEntry|.
+ // If not set then either |mCacheEntry| is not deactivated, or it has been
+ // deactivated but the device returned it from FindEntry() before the event
+ // fired. In both two latter cases this binding is to be considered valid.
+ nsDiskCacheDeviceDeactivateEntryEvent *mDeactivateEvent;
+};
+
+
+/******************************************************************************
+ * Utility Functions
+ *****************************************************************************/
+
+nsDiskCacheBinding * GetCacheEntryBinding(nsCacheEntry * entry);
+
+
+
+/******************************************************************************
+ * nsDiskCacheBindery
+ *
+ * Used to keep track of nsDiskCacheBinding associated with active/bound (and
+ * possibly doomed) entries. Lookups on 4 byte disk hash to find collisions
+ * (which need to be doomed, instead of just evicted. Collisions are linked
+ * using a PRCList to keep track of current generation number.
+ *
+ * Used to detect hash number collisions, and find available generation numbers.
+ *
+ * Not all nsDiskCacheBinding have a generation number.
+ *
+ * Generation numbers may be aquired late, or lost (when data fits in block file)
+ *
+ * Collisions can occur:
+ * BindEntry() - hashnumbers collide (possibly different keys)
+ *
+ * Generation number required:
+ * DeactivateEntry() - metadata written to disk, may require file
+ * GetFileForEntry() - force data to require file
+ * writing to stream - data size may require file
+ *
+ * Binding can be kept in PRCList in order of generation numbers.
+ * Binding with no generation number can be Appended to PRCList (last).
+ *
+ *****************************************************************************/
+
+class nsDiskCacheBindery {
+public:
+ nsDiskCacheBindery();
+ ~nsDiskCacheBindery();
+
+ void Init();
+ void Reset();
+
+ nsDiskCacheBinding * CreateBinding(nsCacheEntry * entry,
+ nsDiskCacheRecord * record);
+
+ nsDiskCacheBinding * FindActiveBinding(uint32_t hashNumber);
+ void RemoveBinding(nsDiskCacheBinding * binding);
+ bool ActiveBindings();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ nsresult AddBinding(nsDiskCacheBinding * binding);
+
+ // member variables
+ static const PLDHashTableOps ops;
+ PLDHashTable table;
+ bool initialized;
+
+ static const uint32_t kInitialTableLength = 0;
+};
+
+#endif /* _nsDiskCacheBinding_h_ */
diff --git a/netwerk/cache/nsDiskCacheBlockFile.cpp b/netwerk/cache/nsDiskCacheBlockFile.cpp
new file mode 100644
index 000000000..6a1f4182f
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBlockFile.cpp
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheBlockFile.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+/******************************************************************************
+ * nsDiskCacheBlockFile -
+ *****************************************************************************/
+
+/******************************************************************************
+ * Open
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::Open(nsIFile * blockFile,
+ uint32_t blockSize,
+ uint32_t bitMapSize,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+
+ if (bitMapSize % 32) {
+ *corruptInfo = nsDiskCache::kInvalidArgPointer;
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mBlockSize = blockSize;
+ mBitMapWords = bitMapSize / 32;
+ uint32_t bitMapBytes = mBitMapWords * 4;
+
+ // open the file - restricted to user, the data could be confidential
+ nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile;
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
+ "[this=%p] unable to open or create file: %d",
+ this, rv));
+ return rv; // unable to open or create file
+ }
+
+ // allocate bit map buffer
+ mBitMap = new uint32_t[mBitMapWords];
+
+ // check if we just creating the file
+ mFileSize = PR_Available(mFD);
+ if (mFileSize < 0) {
+ // XXX an error occurred. We could call PR_GetError(), but how would that help?
+ *corruptInfo = nsDiskCache::kBlockFileSizeError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+ if (mFileSize == 0) {
+ // initialize bit map and write it
+ memset(mBitMap, 0, bitMapBytes);
+ if (!Write(0, mBitMap, bitMapBytes)) {
+ *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError;
+ goto error_exit;
+ }
+
+ } else if ((uint32_t)mFileSize < bitMapBytes) {
+ *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap;
+ rv = NS_ERROR_UNEXPECTED; // XXX NS_ERROR_CACHE_INVALID;
+ goto error_exit;
+
+ } else {
+ // read the bit map
+ const int32_t bytesRead = PR_Read(mFD, mBitMap, bitMapBytes);
+ if ((bytesRead < 0) || ((uint32_t)bytesRead < bitMapBytes)) {
+ *corruptInfo = nsDiskCache::kBlockFileBitMapReadError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+#if defined(IS_LITTLE_ENDIAN)
+ // Swap from network format
+ for (unsigned int i = 0; i < mBitMapWords; ++i)
+ mBitMap[i] = ntohl(mBitMap[i]);
+#endif
+ // validate block file size
+ // Because not whole blocks are written, the size may be a
+ // little bit smaller than used blocks times blocksize,
+ // because the last block will generally not be 'whole'.
+ const uint32_t estimatedSize = CalcBlockFileSize();
+ if ((uint32_t)mFileSize + blockSize < estimatedSize) {
+ *corruptInfo = nsDiskCache::kBlockFileEstimatedSizeError;
+ rv = NS_ERROR_UNEXPECTED;
+ goto error_exit;
+ }
+ }
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
+ this));
+ return NS_OK;
+
+error_exit:
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
+ "error %d", this, rv));
+ Close(false);
+ return rv;
+}
+
+
+/******************************************************************************
+ * Close
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::Close(bool flush)
+{
+ nsresult rv = NS_OK;
+
+ if (mFD) {
+ if (flush)
+ rv = FlushBitMap();
+ PRStatus err = PR_Close(mFD);
+ if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS))
+ rv = NS_ERROR_UNEXPECTED;
+ mFD = nullptr;
+ }
+
+ if (mBitMap) {
+ delete [] mBitMap;
+ mBitMap = nullptr;
+ }
+
+ return rv;
+}
+
+
+/******************************************************************************
+ * AllocateBlocks
+ *
+ * Allocates 1-4 blocks, using a first fit strategy,
+ * so that no group of blocks spans a quad block boundary.
+ *
+ * Returns block number of first block allocated or -1 on failure.
+ *
+ *****************************************************************************/
+int32_t
+nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks)
+{
+ const int maxPos = 32 - numBlocks;
+ const uint32_t mask = (0x01 << numBlocks) - 1;
+ for (unsigned int i = 0; i < mBitMapWords; ++i) {
+ uint32_t mapWord = ~mBitMap[i]; // flip bits so free bits are 1
+ if (mapWord) { // At least one free bit
+ // Binary search for first free bit in word
+ int bit = 0;
+ if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; }
+ if ((mapWord & 0x000FF) == 0) { bit |= 8; mapWord >>= 8; }
+ if ((mapWord & 0x0000F) == 0) { bit |= 4; mapWord >>= 4; }
+ if ((mapWord & 0x00003) == 0) { bit |= 2; mapWord >>= 2; }
+ if ((mapWord & 0x00001) == 0) { bit |= 1; mapWord >>= 1; }
+ // Find first fit for mask
+ for (; bit <= maxPos; ++bit) {
+ // all bits selected by mask are 1, so free
+ if ((mask & mapWord) == mask) {
+ mBitMap[i] |= mask << bit;
+ mBitMapDirty = true;
+ return (int32_t)i * 32 + bit;
+ }
+ }
+ }
+ }
+
+ return -1;
+}
+
+
+/******************************************************************************
+ * DeallocateBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::DeallocateBlocks( int32_t startBlock, int32_t numBlocks)
+{
+ if (!mFD) return NS_ERROR_NOT_AVAILABLE;
+
+ if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
+ (numBlocks < 1) || (numBlocks > 4))
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ const int32_t startWord = startBlock >> 5; // Divide by 32
+ const uint32_t startBit = startBlock & 31; // Modulo by 32
+
+ // make sure requested deallocation doesn't span a word boundary
+ if (startBit + numBlocks > 32) return NS_ERROR_UNEXPECTED;
+ uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
+
+ // make sure requested deallocation is currently allocated
+ if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_ABORT;
+
+ mBitMap[startWord] ^= mask; // flips the bits off;
+ mBitMapDirty = true;
+ // XXX rv = FlushBitMap(); // coherency vs. performance
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * WriteBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::WriteBlocks( void * buffer,
+ uint32_t size,
+ int32_t numBlocks,
+ int32_t * startBlock)
+{
+ // presume buffer != nullptr and startBlock != nullptr
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE);
+
+ // allocate some blocks in the cache block file
+ *startBlock = AllocateBlocks(numBlocks);
+ if (*startBlock < 0)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to block position
+ int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize;
+
+ // write the blocks
+ return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
+/******************************************************************************
+ * ReadBlocks
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::ReadBlocks( void * buffer,
+ int32_t startBlock,
+ int32_t numBlocks,
+ int32_t * bytesRead)
+{
+ // presume buffer != nullptr and bytesRead != bytesRead
+
+ if (!mFD) return NS_ERROR_NOT_AVAILABLE;
+ nsresult rv = VerifyAllocation(startBlock, numBlocks);
+ if (NS_FAILED(rv)) return rv;
+
+ // seek to block position
+ int32_t blockPos = mBitMapWords * 4 + startBlock * mBlockSize;
+ int32_t filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
+ if (filePos != blockPos) return NS_ERROR_UNEXPECTED;
+
+ // read the blocks
+ int32_t bytesToRead = *bytesRead;
+ if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) {
+ bytesToRead = mBlockSize * numBlocks;
+ }
+ *bytesRead = PR_Read(mFD, buffer, bytesToRead);
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
+ "returned %d / %d bytes", this, *bytesRead, bytesToRead));
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * FlushBitMap
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::FlushBitMap()
+{
+ if (!mBitMapDirty) return NS_OK;
+
+#if defined(IS_LITTLE_ENDIAN)
+ uint32_t *bitmap = new uint32_t[mBitMapWords];
+ // Copy and swap to network format
+ uint32_t *p = bitmap;
+ for (unsigned int i = 0; i < mBitMapWords; ++i, ++p)
+ *p = htonl(mBitMap[i]);
+#else
+ uint32_t *bitmap = mBitMap;
+#endif
+
+ // write bitmap
+ bool written = Write(0, bitmap, mBitMapWords * 4);
+#if defined(IS_LITTLE_ENDIAN)
+ delete [] bitmap;
+#endif
+ if (!written)
+ return NS_ERROR_UNEXPECTED;
+
+ PRStatus err = PR_Sync(mFD);
+ if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
+
+ mBitMapDirty = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * VerifyAllocation
+ *
+ * Return values:
+ * NS_OK if all bits are marked allocated
+ * NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
+ * NS_ERROR_FAILURE if some or all the bits are marked unallocated
+ *
+ *****************************************************************************/
+nsresult
+nsDiskCacheBlockFile::VerifyAllocation( int32_t startBlock, int32_t numBlocks)
+{
+ if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
+ (numBlocks < 1) || (numBlocks > 4))
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ const int32_t startWord = startBlock >> 5; // Divide by 32
+ const uint32_t startBit = startBlock & 31; // Modulo by 32
+
+ // make sure requested deallocation doesn't span a word boundary
+ if (startBit + numBlocks > 32) return NS_ERROR_ILLEGAL_VALUE;
+ uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
+
+ // check if all specified blocks are currently allocated
+ if ((mBitMap[startWord] & mask) != mask) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * CalcBlockFileSize
+ *
+ * Return size of the block file according to the bits set in mBitmap
+ *
+ *****************************************************************************/
+uint32_t
+nsDiskCacheBlockFile::CalcBlockFileSize()
+{
+ // search for last byte in mBitMap with allocated bits
+ uint32_t estimatedSize = mBitMapWords * 4;
+ int32_t i = mBitMapWords;
+ while (--i >= 0) {
+ if (mBitMap[i]) break;
+ }
+
+ if (i >= 0) {
+ // binary search to find last allocated bit in byte
+ uint32_t mapWord = mBitMap[i];
+ uint32_t lastBit = 31;
+ if ((mapWord & 0xFFFF0000) == 0) { lastBit ^= 16; mapWord <<= 16; }
+ if ((mapWord & 0xFF000000) == 0) { lastBit ^= 8; mapWord <<= 8; }
+ if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; }
+ if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; }
+ if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; }
+ estimatedSize += (i * 32 + lastBit + 1) * mBlockSize;
+ }
+
+ return estimatedSize;
+}
+
+/******************************************************************************
+ * Write
+ *
+ * Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
+ *
+ *****************************************************************************/
+bool
+nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount)
+{
+ /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
+ 20mb is a magic threshold because OSX stops autodefragging files bigger than that.
+ Beyond 20mb grow in 4mb chunks.
+ */
+ const int32_t upTo = offset + amount;
+ // Use a conservative definition of 20MB
+ const int32_t minPreallocate = 4*1024*1024;
+ const int32_t maxPreallocate = 20*1000*1000;
+ if (mFileSize < upTo) {
+ // maximal file size
+ const int32_t maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1);
+ if (upTo > maxPreallocate) {
+ // grow the file as a multiple of minPreallocate
+ mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate;
+ } else {
+ // Grow quickly between 1MB to 20MB
+ if (mFileSize)
+ while(mFileSize < upTo)
+ mFileSize *= 2;
+ mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate);
+ }
+ mFileSize = std::min(mFileSize, maxFileSize);
+#if !defined(XP_MACOSX)
+ mozilla::fallocate(mFD, mFileSize);
+#endif
+ }
+ if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset)
+ return false;
+ return PR_Write(mFD, buf, amount) == amount;
+}
+
+size_t
+nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
+{
+ return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD);
+}
diff --git a/netwerk/cache/nsDiskCacheBlockFile.h b/netwerk/cache/nsDiskCacheBlockFile.h
new file mode 100644
index 000000000..058cc7fcc
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheBlockFile.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsDiskCacheBlockFile_h_
+#define _nsDiskCacheBlockFile_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIFile.h"
+#include "nsDiskCache.h"
+
+/******************************************************************************
+ * nsDiskCacheBlockFile
+ *
+ * The structure of a cache block file is a 4096 bytes bit map, followed by
+ * some number of blocks of mBlockSize. The creator of a
+ * nsDiskCacheBlockFile object must provide the block size for a given file.
+ *
+ *****************************************************************************/
+class nsDiskCacheBlockFile {
+public:
+ nsDiskCacheBlockFile()
+ : mFD(nullptr)
+ , mBitMap(nullptr)
+ , mBlockSize(0)
+ , mBitMapWords(0)
+ , mFileSize(0)
+ , mBitMapDirty(false)
+ {}
+ ~nsDiskCacheBlockFile() { (void) Close(true); }
+
+ nsresult Open( nsIFile * blockFile, uint32_t blockSize,
+ uint32_t bitMapSize, nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult Close(bool flush);
+
+ /*
+ * Trim
+ * Truncates the block file to the end of the last allocated block.
+ */
+ nsresult Trim() { return nsDiskCache::Truncate(mFD, CalcBlockFileSize()); }
+ nsresult DeallocateBlocks( int32_t startBlock, int32_t numBlocks);
+ nsresult WriteBlocks( void * buffer, uint32_t size, int32_t numBlocks,
+ int32_t * startBlock);
+ nsresult ReadBlocks( void * buffer, int32_t startBlock, int32_t numBlocks,
+ int32_t * bytesRead);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+private:
+ nsresult FlushBitMap();
+ int32_t AllocateBlocks( int32_t numBlocks);
+ nsresult VerifyAllocation( int32_t startBlock, int32_t numBLocks);
+ uint32_t CalcBlockFileSize();
+ bool Write(int32_t offset, const void *buf, int32_t amount);
+
+/**
+ * Data members
+ */
+ PRFileDesc * mFD;
+ uint32_t * mBitMap; // XXX future: array of bit map blocks
+ uint32_t mBlockSize;
+ uint32_t mBitMapWords;
+ int32_t mFileSize;
+ bool mBitMapDirty;
+};
+
+#endif // _nsDiskCacheBlockFile_h_
diff --git a/netwerk/cache/nsDiskCacheDevice.cpp b/netwerk/cache/nsDiskCacheDevice.cpp
new file mode 100644
index 000000000..ac91534ff
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -0,0 +1,1149 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsCache.h"
+#include "nsIMemoryReporter.h"
+
+// include files for ftruncate (or equivalent)
+#if defined(XP_UNIX)
+#include <unistd.h>
+#elif defined(XP_WIN)
+#include <windows.h>
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "prthread.h"
+
+#include "private/pprio.h"
+
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheStreams.h"
+
+#include "nsDiskCache.h"
+
+#include "nsCacheService.h"
+
+#include "nsDeleteDir.h"
+
+#include "nsICacheVisitor.h"
+#include "nsReadableUtils.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCRT.h"
+#include "nsCOMArray.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsThreadUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Telemetry.h"
+
+static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
+using namespace mozilla;
+
+class nsDiskCacheDeviceDeactivateEntryEvent : public Runnable {
+public:
+ nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
+ nsCacheEntry * entry,
+ nsDiskCacheBinding * binding)
+ : mCanceled(false),
+ mEntry(entry),
+ mDevice(device),
+ mBinding(binding)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+ CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
+ if (!mCanceled) {
+ (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
+ }
+ return NS_OK;
+ }
+
+ void CancelEvent() { mCanceled = true; }
+private:
+ bool mCanceled;
+ nsCacheEntry *mEntry;
+ nsDiskCacheDevice *mDevice;
+ nsDiskCacheBinding *mBinding;
+};
+
+class nsEvictDiskCacheEntriesEvent : public Runnable {
+public:
+ explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
+ : mDevice(device) {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+ mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
+ return NS_OK;
+ }
+
+private:
+ nsDiskCacheDevice *mDevice;
+};
+
+/******************************************************************************
+ * nsDiskCacheEvictor
+ *
+ * Helper class for nsDiskCacheDevice.
+ *
+ *****************************************************************************/
+
+class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
+{
+public:
+ nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
+ nsDiskCacheBindery * cacheBindery,
+ uint32_t targetSize,
+ const char * clientID)
+ : mCacheMap(cacheMap)
+ , mBindery(cacheBindery)
+ , mTargetSize(targetSize)
+ , mClientID(clientID)
+ {
+ mClientIDSize = clientID ? strlen(clientID) : 0;
+ }
+
+ virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
+
+private:
+ nsDiskCacheMap * mCacheMap;
+ nsDiskCacheBindery * mBindery;
+ uint32_t mTargetSize;
+ const char * mClientID;
+ uint32_t mClientIDSize;
+};
+
+
+int32_t
+nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
+{
+ if (mCacheMap->TotalSize() < mTargetSize)
+ return kStopVisitingRecords;
+
+ if (mClientID) {
+ // we're just evicting records for a specific client
+ nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
+ if (!diskEntry)
+ return kVisitNextRecord; // XXX or delete record?
+
+ // Compare clientID's without malloc
+ if ((diskEntry->mKeySize <= mClientIDSize) ||
+ (diskEntry->Key()[mClientIDSize] != ':') ||
+ (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
+ return kVisitNextRecord; // clientID doesn't match, skip it
+ }
+ }
+
+ nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
+ if (binding) {
+ // If the entry is pending deactivation, cancel deactivation and doom
+ // the entry
+ if (binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ }
+ // We are currently using this entry, so all we can do is doom it.
+ // Since we're enumerating the records, we don't want to call
+ // DeleteRecord when nsCacheService::DoomEntry() calls us back.
+ binding->mDoomed = true; // mark binding record as 'deleted'
+ nsCacheService::DoomEntry(binding->mCacheEntry);
+ } else {
+ // entry not in use, just delete storage because we're enumerating the records
+ (void) mCacheMap->DeleteStorage(mapRecord);
+ }
+
+ return kDeleteRecordAndContinue; // this will REALLY delete the record
+}
+
+
+/******************************************************************************
+ * nsDiskCacheDeviceInfo
+ *****************************************************************************/
+
+class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
+ : mDevice(device)
+ {
+ }
+
+private:
+ virtual ~nsDiskCacheDeviceInfo() {}
+
+ nsDiskCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
+{
+ NS_ENSURE_ARG_POINTER(aDescription);
+ *aDescription = NS_strdup("Disk cache device");
+ return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
+{
+ NS_ENSURE_ARG_POINTER(usageReport);
+ nsCString buffer;
+
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Cache Directory:</th>\n"
+ " <td>");
+ nsCOMPtr<nsIFile> cacheDir;
+ nsAutoString path;
+ mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
+ nsresult rv = cacheDir->GetPath(path);
+ if (NS_SUCCEEDED(rv)) {
+ AppendUTF16toUTF8(path, buffer);
+ } else {
+ buffer.AppendLiteral("directory unavailable");
+ }
+ buffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ *usageReport = ToNewCString(buffer);
+ if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
+{
+ NS_ENSURE_ARG_POINTER(aEntryCount);
+ *aEntryCount = mDevice->getEntryCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
+{
+ NS_ENSURE_ARG_POINTER(aTotalSize);
+ // Returned unit's are in bytes
+ *aTotalSize = mDevice->getCacheSize() * 1024;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
+{
+ NS_ENSURE_ARG_POINTER(aMaximumSize);
+ // Returned unit's are in bytes
+ *aMaximumSize = mDevice->getCacheCapacity() * 1024;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCache
+ *****************************************************************************/
+
+/**
+ * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
+ *
+ * See http://burtleburtle.net/bob/hash/evahash.html for more information
+ * about this hash function.
+ *
+ * This algorithm of this method implies nsDiskCacheRecords will be stored
+ * in a certain order on disk. If the algorithm changes, existing cache
+ * map files may become invalid, and therefore the kCurrentVersion needs
+ * to be revised.
+ */
+
+static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
+{
+ a -= b; a -= c; a ^= (c>>13);
+ b -= c; b -= a; b ^= (a<<8);
+ c -= a; c -= b; c ^= (b>>13);
+ a -= b; a -= c; a ^= (c>>12);
+ b -= c; b -= a; b ^= (a<<16);
+ c -= a; c -= b; c ^= (b>>5);
+ a -= b; a -= c; a ^= (c>>3);
+ b -= c; b -= a; b ^= (a<<10);
+ c -= a; c -= b; c ^= (b>>15);
+}
+
+PLDHashNumber
+nsDiskCache::Hash(const char * key, PLDHashNumber initval)
+{
+ const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
+ uint32_t a, b, c, len, length;
+
+ length = strlen(key);
+ /* Set up the internal state */
+ len = length;
+ a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+ c = initval; /* variable initialization of internal state */
+
+ /*---------------------------------------- handle most of the key */
+ while (len >= 12)
+ {
+ a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
+ b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
+ c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
+ hashmix(a, b, c);
+ k += 12; len -= 12;
+ }
+
+ /*------------------------------------- handle the last 11 bytes */
+ c += length;
+ switch(len) { /* all the case statements fall through */
+ case 11: c += (uint32_t(k[10])<<24); MOZ_FALLTHROUGH;
+ case 10: c += (uint32_t(k[9])<<16); MOZ_FALLTHROUGH;
+ case 9 : c += (uint32_t(k[8])<<8); MOZ_FALLTHROUGH;
+ /* the low-order byte of c is reserved for the length */
+ case 8 : b += (uint32_t(k[7])<<24); MOZ_FALLTHROUGH;
+ case 7 : b += (uint32_t(k[6])<<16); MOZ_FALLTHROUGH;
+ case 6 : b += (uint32_t(k[5])<<8); MOZ_FALLTHROUGH;
+ case 5 : b += k[4]; MOZ_FALLTHROUGH;
+ case 4 : a += (uint32_t(k[3])<<24); MOZ_FALLTHROUGH;
+ case 3 : a += (uint32_t(k[2])<<16); MOZ_FALLTHROUGH;
+ case 2 : a += (uint32_t(k[1])<<8); MOZ_FALLTHROUGH;
+ case 1 : a += k[0];
+ /* case 0: nothing left to add */
+ }
+ hashmix(a, b, c);
+
+ return c;
+}
+
+nsresult
+nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
+{
+ // use modified SetEOF from nsFileStreams::SetEOF()
+
+#if defined(XP_UNIX)
+ if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#elif defined(XP_WIN)
+ int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
+ if (cnt == -1) return NS_ERROR_FAILURE;
+ if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#else
+ // add implementations for other platforms here
+#endif
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheDevice
+ *****************************************************************************/
+
+nsDiskCacheDevice::nsDiskCacheDevice()
+ : mCacheCapacity(0)
+ , mMaxEntrySize(-1) // -1 means "no limit"
+ , mInitialized(false)
+ , mClearingDiskCache(false)
+{
+}
+
+nsDiskCacheDevice::~nsDiskCacheDevice()
+{
+ Shutdown();
+}
+
+
+/**
+ * methods of nsCacheDevice
+ */
+nsresult
+nsDiskCacheDevice::Init()
+{
+ nsresult rv;
+
+ if (Initialized()) {
+ NS_ERROR("Disk cache already initialized!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mCacheDirectory)
+ return NS_ERROR_FAILURE;
+
+ mBindery.Init();
+
+ // Open Disk Cache
+ rv = OpenDiskCache();
+ if (NS_FAILED(rv)) {
+ (void) mCacheMap.Close(false);
+ return rv;
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::Shutdown()
+{
+ nsCacheService::AssertOwnsLock();
+
+ nsresult rv = Shutdown_Private(true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheDevice::Shutdown_Private(bool flush)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
+
+ if (Initialized()) {
+ // check cache limits in case we need to evict.
+ EvictDiskCacheEntries(mCacheCapacity);
+
+ // At this point there may be a number of pending cache-requests on the
+ // cache-io thread. Wait for all these to run before we wipe out our
+ // datastructures (see bug #620660)
+ (void) nsCacheService::SyncWithCacheIOThread();
+
+ // write out persistent information about the cache.
+ (void) mCacheMap.Close(flush);
+
+ mBindery.Reset();
+
+ mInitialized = false;
+ }
+
+ return NS_OK;
+}
+
+
+const char *
+nsDiskCacheDevice::GetDeviceID()
+{
+ return DISK_CACHE_DEVICE_ID;
+}
+
+/**
+ * FindEntry -
+ *
+ * cases: key not in disk cache, hash number free
+ * key not in disk cache, hash number used
+ * key in disk cache
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsCacheEntry *
+nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
+{
+ Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
+ if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
+ if (mClearingDiskCache) return nullptr;
+ nsDiskCacheRecord record;
+ nsDiskCacheBinding * binding = nullptr;
+ PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
+
+ *collision = false;
+
+ binding = mBindery.FindActiveBinding(hashNumber);
+ if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
+ *collision = true;
+ return nullptr;
+ } else if (binding && binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
+ "req-key=%s entry-key=%s\n",
+ binding->mCacheEntry, key, binding->mCacheEntry->Key()));
+
+ return binding->mCacheEntry; // just return this one, observing that
+ // FindActiveBinding() does not return
+ // bindings to doomed entries
+ }
+ binding = nullptr;
+
+ // lookup hash number in cache map
+ nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
+ if (NS_FAILED(rv)) return nullptr; // XXX log error?
+
+ nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
+ if (!diskEntry) return nullptr;
+
+ // compare key to be sure
+ if (!key->Equals(diskEntry->Key())) {
+ *collision = true;
+ return nullptr;
+ }
+
+ nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
+ if (entry) {
+ binding = mBindery.CreateBinding(entry, &record);
+ if (!binding) {
+ delete entry;
+ entry = nullptr;
+ }
+ }
+
+ if (!entry) {
+ (void) mCacheMap.DeleteStorage(&record);
+ (void) mCacheMap.DeleteRecord(&record);
+ }
+
+ return entry;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
+{
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
+ entry, binding->mRecord.HashNumber()));
+
+ nsDiskCacheDeviceDeactivateEntryEvent *event =
+ new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
+
+ // ensure we can cancel the event via the binding later if necessary
+ binding->mDeactivateEvent = event;
+
+ DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
+ "deactivation event");
+ return NS_OK;
+}
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
+ nsDiskCacheBinding * binding)
+{
+ nsresult rv = NS_OK;
+ if (entry->IsDoomed()) {
+ // delete data, entry, record from disk for entry
+ rv = mCacheMap.DeleteStorage(&binding->mRecord);
+
+ } else {
+ // save stuff to disk for entry
+ rv = mCacheMap.WriteDiskCacheEntry(binding);
+ if (NS_FAILED(rv)) {
+ // clean up as best we can
+ (void) mCacheMap.DeleteStorage(&binding->mRecord);
+ (void) mCacheMap.DeleteRecord(&binding->mRecord);
+ binding->mDoomed = true; // record is no longer in cache map
+ }
+ }
+
+ mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
+ delete entry; // which will release binding
+ return rv;
+}
+
+
+/**
+ * BindEntry()
+ * no hash number collision -> no problem
+ * collision
+ * record not active -> evict, no problem
+ * record is active
+ * record is already doomed -> record shouldn't have been in map, no problem
+ * record is not doomed -> doom, and replace record in map
+ *
+ * walk matching hashnumber list to find lowest generation number
+ * take generation number from other (data/meta) location,
+ * or walk active list
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
+{
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
+ nsresult rv = NS_OK;
+ nsDiskCacheRecord record, oldRecord;
+ nsDiskCacheBinding *binding;
+ PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
+
+ // Find out if there is already an active binding for this hash. If yes it
+ // should have another key since BindEntry() shouldn't be called twice for
+ // the same entry. Doom the old entry, the new one will get another
+ // generation number so files won't collide.
+ binding = mBindery.FindActiveBinding(hashNumber);
+ if (binding) {
+ NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
+ "BindEntry called for already bound entry!");
+ // If the entry is pending deactivation, cancel deactivation
+ if (binding->mDeactivateEvent) {
+ binding->mDeactivateEvent->CancelEvent();
+ binding->mDeactivateEvent = nullptr;
+ }
+ nsCacheService::DoomEntry(binding->mCacheEntry);
+ binding = nullptr;
+ }
+
+ // Lookup hash number in cache map. There can be a colliding inactive entry.
+ // See bug #321361 comment 21 for the scenario. If there is such entry,
+ // delete it.
+ rv = mCacheMap.FindRecord(hashNumber, &record);
+ if (NS_SUCCEEDED(rv)) {
+ nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
+ if (diskEntry) {
+ // compare key to be sure
+ if (!entry->Key()->Equals(diskEntry->Key())) {
+ mCacheMap.DeleteStorage(&record);
+ rv = mCacheMap.DeleteRecord(&record);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ record = nsDiskCacheRecord();
+ }
+
+ // create a new record for this entry
+ record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
+ record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
+
+ CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
+ entry, record.HashNumber()));
+
+ if (!entry->IsDoomed()) {
+ // if entry isn't doomed, add it to the cache map
+ rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t oldHashNumber = oldRecord.HashNumber();
+ if (oldHashNumber) {
+ // gotta evict this one first
+ nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
+ if (oldBinding) {
+ // XXX if debug : compare keys for hashNumber collision
+
+ if (!oldBinding->mCacheEntry->IsDoomed()) {
+ // If the old entry is pending deactivation, cancel deactivation
+ if (oldBinding->mDeactivateEvent) {
+ oldBinding->mDeactivateEvent->CancelEvent();
+ oldBinding->mDeactivateEvent = nullptr;
+ }
+ // we've got a live one!
+ nsCacheService::DoomEntry(oldBinding->mCacheEntry);
+ // storage will be delete when oldBinding->mCacheEntry is Deactivated
+ }
+ } else {
+ // delete storage
+ // XXX if debug : compare keys for hashNumber collision
+ rv = mCacheMap.DeleteStorage(&oldRecord);
+ if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
+ }
+ }
+ }
+
+ // Make sure this entry has its associated nsDiskCacheBinding attached.
+ binding = mBindery.CreateBinding(entry, &record);
+ NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
+ if (!binding) return NS_ERROR_OUT_OF_MEMORY;
+ NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
+
+ return NS_OK;
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+void
+nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
+ if (!binding)
+ return;
+
+ if (!binding->mDoomed) {
+ // so it can't be seen by FindEntry() ever again.
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ mCacheMap.DeleteRecord(&binding->mRecord);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
+ binding->mDoomed = true; // record in no longer in cache map
+ }
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
+ entry, mode, offset));
+
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
+
+ rv = binding->EnsureStreamIO();
+ if (NS_FAILED(rv)) return rv;
+
+ return binding->mStreamIO->GetInputStream(offset, result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
+ entry, mode, offset));
+
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv;
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
+
+ rv = binding->EnsureStreamIO();
+ if (NS_FAILED(rv)) return rv;
+
+ return binding->mStreamIO->GetOutputStream(offset, result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = nullptr;
+
+ nsresult rv;
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ // check/set binding->mRecord for separate file, sync w/mCacheMap
+ if (binding->mRecord.DataLocationInitialized()) {
+ if (binding->mRecord.DataFile() != 0)
+ return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
+
+ NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
+ } else {
+ binding->mRecord.SetDataFileGeneration(binding->mGeneration);
+ binding->mRecord.SetDataFileSize(0); // 1k minimum
+ if (!binding->mDoomed) {
+ // record stored in cache map, so update it
+ rv = mCacheMap.UpdateRecord(&binding->mRecord);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
+ nsDiskCache::kData,
+ false,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return NS_OK;
+}
+
+
+/**
+ * This routine will get called every time an open descriptor is written to.
+ *
+ * NOTE: called while holding the cache service lock
+ */
+nsresult
+nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
+ entry, deltaSize));
+
+ // If passed a negative value, then there's nothing to do.
+ if (deltaSize < 0)
+ return NS_OK;
+
+ nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
+ if (!IsValidBinding(binding))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
+
+ uint32_t newSize = entry->DataSize() + deltaSize;
+ uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
+
+ // If the new size is larger than max. file size or larger than
+ // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
+ if (EntryIsTooBig(newSize)) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+
+ uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
+
+ // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
+ // the target capacity should be calculated the same way.
+ if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
+ if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
+
+ // pre-evict entries to make space for new data
+ uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
+ ? mCacheCapacity - (newSizeK - sizeK)
+ : 0;
+ EvictDiskCacheEntries(targetCapacity);
+
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * EntryInfoVisitor
+ *****************************************************************************/
+class EntryInfoVisitor : public nsDiskCacheRecordVisitor
+{
+public:
+ EntryInfoVisitor(nsDiskCacheMap * cacheMap,
+ nsICacheVisitor * visitor)
+ : mCacheMap(cacheMap)
+ , mVisitor(visitor)
+ {}
+
+ virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
+ {
+ // XXX optimization: do we have this record in memory?
+
+ // read in the entry (metadata)
+ nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
+ if (!diskEntry) {
+ return kVisitNextRecord;
+ }
+
+ // create nsICacheEntryInfo
+ nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
+ if (!entryInfo) {
+ return kStopVisitingRecords;
+ }
+ nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
+
+ bool keepGoing;
+ (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
+ return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
+ }
+
+private:
+ nsDiskCacheMap * mCacheMap;
+ nsICacheVisitor * mVisitor;
+};
+
+
+nsresult
+nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
+{
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
+ nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
+ if (NS_FAILED(rv)) return rv;
+
+ if (keepGoing) {
+ EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
+ return mCacheMap.VisitRecords(&infoVisitor);
+ }
+
+ return NS_OK;
+}
+
+// Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
+bool
+nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
+{
+ if (mMaxEntrySize == -1) // no limit
+ return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
+ else
+ return entrySize > mMaxEntrySize ||
+ entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
+}
+
+nsresult
+nsDiskCacheDevice::EvictEntries(const char * clientID)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
+
+ if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
+ nsresult rv;
+
+ if (clientID == nullptr) {
+ // we're clearing the entire disk cache
+ rv = ClearDiskCache();
+ if (rv != NS_ERROR_CACHE_IN_USE)
+ return rv;
+ }
+
+ nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
+ rv = mCacheMap.VisitRecords(&evictor);
+
+ if (clientID == nullptr) // we tried to clear the entire cache
+ rv = mCacheMap.Trim(); // so trim cache block files (if possible)
+ return rv;
+}
+
+
+/**
+ * private methods
+ */
+
+nsresult
+nsDiskCacheDevice::OpenDiskCache()
+{
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
+ // if we don't have a cache directory, create one and open it
+ bool exists;
+ nsresult rv = mCacheDirectory->Exists(&exists);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (exists) {
+ // Try opening cache map file.
+ nsDiskCache::CorruptCacheInfo corruptInfo;
+ rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
+
+ if (rv == NS_ERROR_ALREADY_INITIALIZED) {
+ NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
+ } else if (NS_FAILED(rv)) {
+ // Consider cache corrupt: delete it
+ // delay delete by 1 minute to avoid IO thrash at startup
+ rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
+ if (NS_FAILED(rv))
+ return rv;
+ exists = false;
+ }
+ }
+
+ // if we don't have a cache directory, create one and open it
+ if (!exists) {
+ nsCacheService::MarkStartingFresh();
+ rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ CACHE_LOG_PATH(LogLevel::Info, "\ncreate cache directory: %s\n", mCacheDirectory);
+ CACHE_LOG_INFO(("mCacheDirectory->Create() = %x\n", rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // reopen the cache map
+ nsDiskCache::CorruptCacheInfo corruptInfo;
+ rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheDevice::ClearDiskCache()
+{
+ if (mBindery.ActiveBindings())
+ return NS_ERROR_CACHE_IN_USE;
+
+ mClearingDiskCache = true;
+
+ nsresult rv = Shutdown_Private(false); // false: don't bother flushing
+ if (NS_FAILED(rv))
+ return rv;
+
+ mClearingDiskCache = false;
+
+ // If the disk cache directory is already gone, then it's not an error if
+ // we fail to delete it ;-)
+ rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+
+ return Init();
+}
+
+
+nsresult
+nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
+{
+ CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
+ targetCapacity));
+
+ NS_ASSERTION(targetCapacity > 0, "oops");
+
+ if (mCacheMap.TotalSize() < targetCapacity)
+ return NS_OK;
+
+ // targetCapacity is in KiB's
+ nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
+ return mCacheMap.EvictRecords(&evictor);
+}
+
+
+/**
+ * methods for prefs
+ */
+
+void
+nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
+{
+ nsresult rv;
+ bool exists;
+
+ if (Initialized()) {
+ NS_ASSERTION(false, "Cannot switch cache directory when initialized");
+ return;
+ }
+
+ if (!parentDir) {
+ mCacheDirectory = nullptr;
+ return;
+ }
+
+ // ensure parent directory exists
+ rv = parentDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) return;
+
+ // ensure cache directory exists
+ nsCOMPtr<nsIFile> directory;
+
+ rv = parentDir->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) return;
+ rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_FAILED(rv)) return;
+
+ mCacheDirectory = do_QueryInterface(directory);
+}
+
+
+void
+nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
+{
+ *result = mCacheDirectory;
+ NS_IF_ADDREF(*result);
+}
+
+
+/**
+ * NOTE: called while holding the cache service lock
+ */
+void
+nsDiskCacheDevice::SetCapacity(uint32_t capacity)
+{
+ // Units are KiB's
+ mCacheCapacity = capacity;
+ if (Initialized()) {
+ if (NS_IsMainThread()) {
+ // Do not evict entries on the main thread
+ nsCacheService::DispatchToCacheIOThread(
+ new nsEvictDiskCacheEntriesEvent(this));
+ } else {
+ // start evicting entries if the new size is smaller!
+ EvictDiskCacheEntries(mCacheCapacity);
+ }
+ }
+ // Let cache map know of the new capacity
+ mCacheMap.NotifyCapacityChange(capacity);
+}
+
+
+uint32_t nsDiskCacheDevice::getCacheCapacity()
+{
+ return mCacheCapacity;
+}
+
+
+uint32_t nsDiskCacheDevice::getCacheSize()
+{
+ return mCacheMap.TotalSize();
+}
+
+
+uint32_t nsDiskCacheDevice::getEntryCount()
+{
+ return mCacheMap.EntryCount();
+}
+
+void
+nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
+{
+ // Internal units are bytes. Changing this only takes effect *after* the
+ // change and has no consequences for existing cache-entries
+ if (maxSizeInKilobytes >= 0)
+ mMaxEntrySize = maxSizeInKilobytes * 1024;
+ else
+ mMaxEntrySize = -1;
+}
+
+size_t
+nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(this);
+
+ usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
+ usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
+
+ return usage;
+}
diff --git a/netwerk/cache/nsDiskCacheDevice.h b/netwerk/cache/nsDiskCacheDevice.h
new file mode 100644
index 000000000..f986154e6
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDevice.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsDiskCacheDevice_h_
+#define _nsDiskCacheDevice_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsCacheDevice.h"
+#include "nsDiskCacheBinding.h"
+#include "nsDiskCacheBlockFile.h"
+#include "nsDiskCacheEntry.h"
+
+#include "nsIFile.h"
+#include "nsIObserver.h"
+#include "nsCOMArray.h"
+
+class nsDiskCacheMap;
+
+
+class nsDiskCacheDevice final : public nsCacheDevice {
+public:
+ nsDiskCacheDevice();
+ virtual ~nsDiskCacheDevice();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual const char * GetDeviceID(void);
+ virtual nsCacheEntry * FindEntry(nsCString * key, bool *collision);
+ virtual nsresult DeactivateEntry(nsCacheEntry * entry);
+ virtual nsresult BindEntry(nsCacheEntry * entry);
+ virtual void DoomEntry( nsCacheEntry * entry );
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result);
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result);
+
+ virtual nsresult GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result);
+
+ virtual nsresult OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize);
+
+ virtual nsresult Visit(nsICacheVisitor * visitor);
+
+ virtual nsresult EvictEntries(const char * clientID);
+
+ bool EntryIsTooBig(int64_t entrySize);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ /**
+ * Preference accessors
+ */
+ void SetCacheParentDirectory(nsIFile * parentDir);
+ void SetCapacity(uint32_t capacity);
+ void SetMaxEntrySize(int32_t maxSizeInKilobytes);
+
+/* private: */
+
+ void getCacheDirectory(nsIFile ** result);
+ uint32_t getCacheCapacity();
+ uint32_t getCacheSize();
+ uint32_t getEntryCount();
+
+ nsDiskCacheMap * CacheMap() { return &mCacheMap; }
+
+private:
+ friend class nsDiskCacheDeviceDeactivateEntryEvent;
+ friend class nsEvictDiskCacheEntriesEvent;
+ friend class nsDiskCacheMap;
+ /**
+ * Private methods
+ */
+
+ inline bool IsValidBinding(nsDiskCacheBinding *binding)
+ {
+ NS_ASSERTION(binding, " binding == nullptr");
+ NS_ASSERTION(binding->mDeactivateEvent == nullptr,
+ " entry in process of deactivation");
+ return (binding && !binding->mDeactivateEvent);
+ }
+
+ bool Initialized() { return mInitialized; }
+
+ nsresult Shutdown_Private(bool flush);
+ nsresult DeactivateEntry_Private(nsCacheEntry * entry,
+ nsDiskCacheBinding * binding);
+
+ nsresult OpenDiskCache();
+ nsresult ClearDiskCache();
+
+ nsresult EvictDiskCacheEntries(uint32_t targetCapacity);
+
+ /**
+ * Member variables
+ */
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ nsDiskCacheBindery mBindery;
+ uint32_t mCacheCapacity; // Unit is KiB's
+ int32_t mMaxEntrySize; // Unit is bytes internally
+ // XXX need soft/hard limits, currentTotal
+ nsDiskCacheMap mCacheMap;
+ bool mInitialized;
+ bool mClearingDiskCache;
+};
+
+#endif // _nsDiskCacheDevice_h_
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
new file mode 100644
index 000000000..56ece5887
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -0,0 +1,2906 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <inttypes.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ThreadLocal.h"
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheService.h"
+#include "nsApplicationCache.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsAutoPtr.h"
+#include "nsEscape.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsCRT.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
+#include "nsIVariant.h"
+#include "nsILoadContextInfo.h"
+#include "nsThreadUtils.h"
+#include "nsISerializable.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsSerializationHelper.h"
+
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageFunction.h"
+#include "mozStorageHelper.h"
+
+#include "nsICacheVisitor.h"
+#include "nsISeekableStream.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "sqlite3.h"
+#include "mozilla/storage.h"
+#include "nsVariant.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::storage;
+using mozilla::NeckoOriginAttributes;
+
+static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
+
+#define LOG(args) CACHE_LOG_DEBUG(args)
+
+static uint32_t gNextTemporaryClientID = 0;
+
+/*****************************************************************************
+ * helpers
+ */
+
+static nsresult
+EnsureDir(nsIFile *dir)
+{
+ bool exists;
+ nsresult rv = dir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ return rv;
+}
+
+static bool
+DecomposeCacheEntryKey(const nsCString *fullKey,
+ const char **cid,
+ const char **key,
+ nsCString &buf)
+{
+ buf = *fullKey;
+
+ int32_t colon = buf.FindChar(':');
+ if (colon == kNotFound)
+ {
+ NS_ERROR("Invalid key");
+ return false;
+ }
+ buf.SetCharAt('\0', colon);
+
+ *cid = buf.get();
+ *key = buf.get() + colon + 1;
+
+ return true;
+}
+
+class AutoResetStatement
+{
+ public:
+ explicit AutoResetStatement(mozIStorageStatement *s)
+ : mStatement(s) {}
+ ~AutoResetStatement() { mStatement->Reset(); }
+ mozIStorageStatement *operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mStatement; }
+ private:
+ mozIStorageStatement *mStatement;
+};
+
+class EvictionObserver
+{
+ public:
+ EvictionObserver(mozIStorageConnection *db,
+ nsOfflineCacheEvictionFunction *evictionFunction)
+ : mDB(db), mEvictionFunction(evictionFunction)
+ {
+ mEvictionFunction->Init();
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
+ " ON moz_cache FOR EACH ROW BEGIN SELECT"
+ " cache_eviction_observer("
+ " OLD.ClientID, OLD.key, OLD.generation);"
+ " END;"));
+ }
+
+ ~EvictionObserver()
+ {
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
+ mEvictionFunction->Reset();
+ }
+
+ void Apply() { return mEvictionFunction->Apply(); }
+
+ private:
+ mozIStorageConnection *mDB;
+ RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+};
+
+#define DCACHE_HASH_MAX INT64_MAX
+#define DCACHE_HASH_BITS 64
+
+/**
+ * nsOfflineCache::Hash(const char * key)
+ *
+ * This algorithm of this method implies nsOfflineCacheRecords will be stored
+ * in a certain order on disk. If the algorithm changes, existing cache
+ * map files may become invalid, and therefore the kCurrentVersion needs
+ * to be revised.
+ */
+static uint64_t
+DCacheHash(const char * key)
+{
+ // initval 0x7416f295 was chosen randomly
+ return (uint64_t(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
+}
+
+/******************************************************************************
+ * nsOfflineCacheEvictionFunction
+ */
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
+
+// helper function for directly exposing the same data file binding
+// path algorithm used in nsOfflineCacheBinding::Create
+static nsresult
+GetCacheDataFile(nsIFile *cacheDir, const char *key,
+ int generation, nsCOMPtr<nsIFile> &file)
+{
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t) (hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ file->AppendNative(nsPrintfCString("%X", dir2));
+
+ char leaf[64];
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ return file->AppendNative(nsDependentCString(leaf));
+}
+
+namespace appcachedetail {
+
+typedef nsCOMArray<nsIFile> FileArray;
+static MOZ_THREAD_LOCAL(FileArray*) tlsEvictionItems;
+
+} // appcachedetail
+
+NS_IMETHODIMP
+nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
+{
+ LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
+
+ *_retval = nullptr;
+
+ uint32_t numEntries;
+ nsresult rv = values->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
+
+ uint32_t valueLen;
+ const char *clientID = values->AsSharedUTF8String(0, &valueLen);
+ const char *key = values->AsSharedUTF8String(1, &valueLen);
+ nsAutoCString fullKey(clientID);
+ fullKey.Append(':');
+ fullKey.Append(key);
+ int generation = values->AsInt32(2);
+
+ // If the key is currently locked, refuse to delete this row.
+ if (mDevice->IsLocked(fullKey)) {
+ NS_ADDREF(*_retval = new IntegerVariant(SQLITE_IGNORE));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
+ generation, file);
+ if (NS_FAILED(rv))
+ {
+ LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
+ key, generation, rv));
+ return rv;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ MOZ_ASSERT(items);
+ if (items) {
+ items->AppendObject(file);
+ }
+
+ return NS_OK;
+}
+
+nsOfflineCacheEvictionFunction::nsOfflineCacheEvictionFunction(nsOfflineCacheDevice * device)
+ : mDevice(device)
+{
+ mTLSInited = appcachedetail::tlsEvictionItems.init();
+}
+
+void nsOfflineCacheEvictionFunction::Init()
+{
+ if (mTLSInited) {
+ appcachedetail::tlsEvictionItems.set(new appcachedetail::FileArray());
+ }
+}
+
+void nsOfflineCacheEvictionFunction::Reset()
+{
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ if (!items) {
+ return;
+ }
+
+ appcachedetail::tlsEvictionItems.set(nullptr);
+ delete items;
+}
+
+void
+nsOfflineCacheEvictionFunction::Apply()
+{
+ LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
+
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* pitems = appcachedetail::tlsEvictionItems.get();
+ if (!pitems) {
+ return;
+ }
+
+ appcachedetail::FileArray items;
+ items.SwapElements(*pitems);
+
+ for (int32_t i = 0; i < items.Count(); i++) {
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ items[i]->GetNativePath(path);
+ LOG((" removing %s\n", path.get()));
+ }
+
+ items[i]->Remove(false);
+ }
+}
+
+class nsOfflineCacheDiscardCache : public Runnable
+{
+public:
+ nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device,
+ nsCString &group,
+ nsCString &clientID)
+ : mDevice(device)
+ , mGroup(group)
+ , mClientID(clientID)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mDevice->IsActiveCache(mGroup, mClientID))
+ {
+ mDevice->DeactivateGroup(mGroup);
+ }
+
+ return mDevice->EvictEntries(mClientID.get());
+ }
+
+private:
+ RefPtr<nsOfflineCacheDevice> mDevice;
+ nsCString mGroup;
+ nsCString mClientID;
+};
+
+/******************************************************************************
+ * nsOfflineCacheDeviceInfo
+ */
+
+class nsOfflineCacheDeviceInfo final : public nsICacheDeviceInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
+ : mDevice(device)
+ {}
+
+private:
+ ~nsOfflineCacheDeviceInfo() {}
+
+ nsOfflineCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
+{
+ *aDescription = NS_strdup("Offline cache device");
+ return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
+{
+ nsAutoCString buffer;
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Cache Directory:</th>\n"
+ " <td>");
+ nsIFile *cacheDir = mDevice->CacheDirectory();
+ if (!cacheDir)
+ return NS_OK;
+
+ nsAutoString path;
+ nsresult rv = cacheDir->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ AppendUTF16toUTF8(path, buffer);
+ else
+ buffer.AppendLiteral("directory unavailable");
+
+ buffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ *usageReport = ToNewCString(buffer);
+ if (!*usageReport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
+{
+ *aEntryCount = mDevice->EntryCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
+{
+ *aTotalSize = mDevice->CacheSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
+{
+ *aMaximumSize = mDevice->CacheCapacity();
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsOfflineCacheBinding
+ */
+
+class nsOfflineCacheBinding final : public nsISupports
+{
+ ~nsOfflineCacheBinding() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static nsOfflineCacheBinding *
+ Create(nsIFile *cacheDir, const nsCString *key, int generation);
+
+ enum { FLAG_NEW_ENTRY = 1 };
+
+ nsCOMPtr<nsIFile> mDataFile;
+ int mGeneration;
+ int mFlags;
+
+ bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
+ void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
+ void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
+};
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
+
+nsOfflineCacheBinding *
+nsOfflineCacheBinding::Create(nsIFile *cacheDir,
+ const nsCString *fullKey,
+ int generation)
+{
+ nsCOMPtr<nsIFile> file;
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file)
+ return nullptr;
+
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
+ return nullptr;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t) (hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ // XXX we might want to create these directories up-front
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ file->AppendNative(nsPrintfCString("%X", dir2));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ nsresult rv;
+ char leaf[64];
+
+ if (generation == -1)
+ {
+ file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
+
+ for (generation = 0; ; ++generation)
+ {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+
+ rv = file->SetNativeLeafName(nsDependentCString(leaf));
+ if (NS_FAILED(rv))
+ return nullptr;
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ return nullptr;
+ if (NS_SUCCEEDED(rv))
+ break;
+ }
+ }
+ else
+ {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ rv = file->AppendNative(nsDependentCString(leaf));
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
+ if (!binding)
+ return nullptr;
+
+ binding->mDataFile.swap(file);
+ binding->mGeneration = generation;
+ binding->mFlags = 0;
+ return binding;
+}
+
+/******************************************************************************
+ * nsOfflineCacheRecord
+ */
+
+struct nsOfflineCacheRecord
+{
+ const char *clientID;
+ const char *key;
+ const uint8_t *metaData;
+ uint32_t metaDataLen;
+ int32_t generation;
+ int32_t dataSize;
+ int32_t fetchCount;
+ int64_t lastFetched;
+ int64_t lastModified;
+ int64_t expirationTime;
+};
+
+static nsCacheEntry *
+CreateCacheEntry(nsOfflineCacheDevice *device,
+ const nsCString *fullKey,
+ const nsOfflineCacheRecord &rec)
+{
+ nsCacheEntry *entry;
+
+ if (device->IsLocked(*fullKey)) {
+ return nullptr;
+ }
+
+ nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
+ nsICache::STREAM_BASED,
+ nsICache::STORE_OFFLINE,
+ device, &entry);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ entry->SetFetchCount((uint32_t) rec.fetchCount);
+ entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
+ entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
+ entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
+ entry->SetDataSize((uint32_t) rec.dataSize);
+
+ entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
+
+ // Restore security info, if present
+ const char* info = entry->GetMetaDataElement("security-info");
+ if (info) {
+ nsCOMPtr<nsISupports> infoObj;
+ rv = NS_DeserializeObject(nsDependentCString(info),
+ getter_AddRefs(infoObj));
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetSecurityInfo(infoObj);
+ }
+
+ // create a binding object for this entry
+ nsOfflineCacheBinding *binding =
+ nsOfflineCacheBinding::Create(device->CacheDirectory(),
+ fullKey,
+ rec.generation);
+ if (!binding)
+ {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetData(binding);
+
+ return entry;
+}
+
+
+/******************************************************************************
+ * nsOfflineCacheEntryInfo
+ */
+
+class nsOfflineCacheEntryInfo final : public nsICacheEntryInfo
+{
+ ~nsOfflineCacheEntryInfo() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsOfflineCacheRecord *mRec;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetClientID(char **result)
+{
+ *result = NS_strdup(mRec->clientID);
+ return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
+{
+ clientKey.Assign(mRec->key);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
+{
+ *aFetchCount = mRec->fetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
+{
+ *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
+{
+ *aLastModified = SecondsFromPRTime(mRec->lastModified);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
+{
+ *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
+{
+ *aStreamBased = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
+{
+ *aDataSize = mRec->dataSize;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsApplicationCacheNamespace
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::Init(uint32_t itemType,
+ const nsACString &namespaceSpec,
+ const nsACString &data)
+{
+ mItemType = itemType;
+ mNamespaceSpec = namespaceSpec;
+ mData = data;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetItemType(uint32_t *out)
+{
+ *out = mItemType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
+{
+ out = mNamespaceSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetData(nsACString &out)
+{
+ out = mData;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsApplicationCache
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCache,
+ nsIApplicationCache,
+ nsISupportsWeakReference)
+
+nsApplicationCache::nsApplicationCache()
+ : mDevice(nullptr)
+ , mValid(true)
+{
+}
+
+nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
+ const nsACString &group,
+ const nsACString &clientID)
+ : mDevice(device)
+ , mGroup(group)
+ , mClientID(clientID)
+ , mValid(true)
+{
+}
+
+nsApplicationCache::~nsApplicationCache()
+{
+ if (!mDevice)
+ return;
+
+ {
+ MutexAutoLock lock(mDevice->mLock);
+ mDevice->mCaches.Remove(mClientID);
+ }
+
+ // If this isn't an active cache anymore, it can be destroyed.
+ if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
+ Discard();
+}
+
+void
+nsApplicationCache::MarkInvalid()
+{
+ mValid = false;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::InitAsHandle(const nsACString &groupId,
+ const nsACString &clientId)
+{
+ NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
+
+ mGroup = groupId;
+ mClientID = clientId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetManifestURI(nsIURI **out)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uri->CloneIgnoringRef(out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetGroupID(nsACString &out)
+{
+ out = mGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetClientID(nsACString &out)
+{
+ out = mClientID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetProfileDirectory(nsIFile **out)
+{
+ if (mDevice->BaseDirectory())
+ NS_ADDREF(*out = mDevice->BaseDirectory());
+ else
+ *out = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetActive(bool *out)
+{
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ *out = mDevice->IsActiveCache(mGroup, mClientID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Activate()
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mDevice->ActivateCache(mGroup, mClientID);
+
+ if (mDevice->AutoShutdown(this))
+ mDevice = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Discard()
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mValid = false;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
+ nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::MarkEntry(const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->MarkEntry(mClientID, key, typeBits);
+}
+
+
+NS_IMETHODIMP
+nsApplicationCache::UnmarkEntry(const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->UnmarkEntry(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetTypes(const nsACString &key,
+ uint32_t *typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetTypes(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GatherEntries(uint32_t typeBits,
+ uint32_t * count,
+ char *** keys)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GatherEntries(mClientID, typeBits, count, keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::AddNamespaces(nsIArray *namespaces)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ if (!namespaces)
+ return NS_OK;
+
+ mozStorageTransaction transaction(mDevice->mDB, false);
+
+ uint32_t length;
+ nsresult rv = namespaces->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ do_QueryElementAt(namespaces, i);
+ if (ns) {
+ rv = mDevice->AddNamespace(mClientID, ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetMatchingNamespace(const nsACString &key,
+ nsIApplicationCacheNamespace **out)
+
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetMatchingNamespace(mClientID, key, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetUsage(uint32_t *usage)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetUsage(mClientID, usage);
+}
+
+/******************************************************************************
+ * nsCloseDBEvent
+ *****************************************************************************/
+
+class nsCloseDBEvent : public Runnable {
+public:
+ explicit nsCloseDBEvent(mozIStorageConnection *aDB)
+ {
+ mDB = aDB;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mDB->Close();
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsCloseDBEvent() {}
+
+private:
+ nsCOMPtr<mozIStorageConnection> mDB;
+};
+
+
+
+/******************************************************************************
+ * nsOfflineCacheDevice
+ */
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
+
+nsOfflineCacheDevice::nsOfflineCacheDevice()
+ : mDB(nullptr)
+ , mCacheCapacity(0)
+ , mDeltaCounter(0)
+ , mAutoShutdown(false)
+ , mLock("nsOfflineCacheDevice.lock")
+ , mActiveCaches(4)
+ , mLockedEntries(32)
+{
+}
+
+nsOfflineCacheDevice::~nsOfflineCacheDevice()
+{}
+
+/* static */
+bool
+nsOfflineCacheDevice::GetStrictFileOriginPolicy()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ bool retval;
+ if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
+ return retval;
+
+ // As default value use true (be more strict)
+ return true;
+}
+
+uint32_t
+nsOfflineCacheDevice::CacheSize()
+{
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_CacheSize);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t) statement->AsInt32(0);
+}
+
+uint32_t
+nsOfflineCacheDevice::EntryCount()
+{
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_EntryCount);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t) statement->AsInt32(0);
+}
+
+nsresult
+nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // Store security info, if it is serializable
+ nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+ if (infoObj && !serializable)
+ return NS_ERROR_UNEXPECTED;
+
+ if (serializable) {
+ nsCString info;
+ nsresult rv = NS_SerializeToString(serializable, info);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = entry->SetMetaDataElement("security-info", info.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString metaDataBuf;
+ uint32_t mdSize = entry->MetaDataSize();
+ if (!metaDataBuf.SetLength(mdSize, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+ char *md = metaDataBuf.BeginWriting();
+ entry->FlattenMetaData(md, mdSize);
+
+ nsOfflineCacheRecord rec;
+ rec.metaData = (const uint8_t *) md;
+ rec.metaDataLen = mdSize;
+ rec.dataSize = entry->DataSize();
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_UpdateEntry);
+
+ nsresult rv;
+ rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
+ nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(4, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_UpdateEntrySize);
+
+ nsresult rv = statement->BindInt32ByIndex(0, newSize);
+ nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ if (deleteData)
+ {
+ nsresult rv = DeleteData(entry);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_DeleteEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "DELETE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
+{
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ return binding->mDataFile->Remove(false);
+}
+
+/**
+ * nsCacheDevice implementation
+ */
+
+// This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
+// allow a template (mozilla::ArrayLength) to be instantiated based on a local
+// type. Boo-urns!
+struct StatementSql {
+ nsCOMPtr<mozIStorageStatement> &statement;
+ const char *sql;
+ StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
+ statement (aStatement), sql (aSql) {}
+};
+
+nsresult
+nsOfflineCacheDevice::Init()
+{
+ MOZ_ASSERT(false, "Need to be initialized with sqlite");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsOfflineCacheDevice::InitWithSqlite(mozIStorageService * ss)
+{
+ NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
+
+ // SetCacheParentDirectory must have been called
+ NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
+
+ // make sure the cache directory exists
+ nsresult rv = EnsureDir(mCacheDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build path to index file
+ nsCOMPtr<nsIFile> indexFile;
+ rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(ss, "nsOfflineCacheDevice::InitWithSqlite called before nsCacheService::Init() ?");
+ NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
+
+ rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitThread = do_GetCurrentThread();
+
+ mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
+
+ // XXX ... other initialization steps
+
+ // XXX in the future we may wish to verify the schema for moz_cache
+ // perhaps using "PRAGMA table_info" ?
+
+ // build the table
+ //
+ // "Generation" is the data file generation number.
+ //
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
+ " ClientID TEXT,\n"
+ " Key TEXT,\n"
+ " MetaData BLOB,\n"
+ " Generation INTEGER,\n"
+ " DataSize INTEGER,\n"
+ " FetchCount INTEGER,\n"
+ " LastFetched INTEGER,\n"
+ " LastModified INTEGER,\n"
+ " ExpirationTime INTEGER,\n"
+ " ItemType INTEGER DEFAULT 0\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 don't have the ItemType column. Add the column
+ // here, but don't worry about failures (the column probably already exists)
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
+
+ // Create the table for storing cache groups. All actions on
+ // moz_cache_groups use the GroupID, so use it as the primary key.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
+ " GroupID TEXT PRIMARY KEY,\n"
+ " ActiveClientID TEXT\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
+ "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
+
+ // ClientID: clientID joining moz_cache and moz_cache_namespaces
+ // tables.
+ // Data: Data associated with this namespace (e.g. a fallback URI
+ // for fallback entries).
+ // ItemType: the type of namespace.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
+ " moz_cache_namespaces (\n"
+ " ClientID TEXT,\n"
+ " NameSpace TEXT,\n"
+ " Data TEXT,\n"
+ " ItemType INTEGER\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 have a moz_cache_index that should be dropped
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Key/ClientID pairs should be unique in the database. All queries
+ // against moz_cache use the Key (which is also the most unique), so
+ // use it as the primary key for this index.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
+ " moz_cache_key_clientid_index"
+ " ON moz_cache (Key, ClientID);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_clientid_index"
+ " ON moz_cache_namespaces (ClientID, NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for namespace lookups.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_namespace_index"
+ " ON moz_cache_namespaces (NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
+ if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create all (most) of our statements up front
+ StatementSql prepared[] = {
+ StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
+ StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
+ StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
+ StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
+
+ StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
+ StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
+ StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
+
+ StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
+ StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
+ StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
+
+ // Search for namespaces that match the URI. Use the <= operator
+ // to ensure that we use the index on moz_cache_namespaces.
+ StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
+ " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
+ " ON ns.ClientID = groups.ActiveClientID"
+ " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
+ " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
+ StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
+ " WHERE ClientID = ?1"
+ " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
+ " ORDER BY NameSpace DESC;"),
+ StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
+ StatementSql ( mStatement_EnumerateApps, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
+ StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
+ StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
+ };
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i)
+ {
+ LOG(("Creating statement: %s\n", prepared[i].sql));
+
+ rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
+ getter_AddRefs(prepared[i].statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitActiveCaches();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+namespace {
+
+nsresult
+GetGroupForCache(const nsCSubstring &clientID, nsCString &group)
+{
+ group.Assign(clientID);
+ group.Truncate(group.FindChar('|'));
+ NS_UnescapeURL(group);
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+nsresult
+nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL,
+ nsACString const &aOriginSuffix,
+ nsACString &_result)
+{
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString manifestSpec;
+ rv = newURI->GetAsciiSpec(manifestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ _result.Assign(manifestSpec);
+ _result.Append('#');
+ _result.Append(aOriginSuffix);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::InitActiveCaches()
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ MutexAutoLock lock(mLock);
+
+ AutoResetStatement statement(mStatement_EnumerateGroups);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows)
+ {
+ nsAutoCString group;
+ statement->GetUTF8String(0, group);
+ nsCString clientID;
+ statement->GetUTF8String(1, clientID);
+
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Shutdown()
+{
+ NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
+
+ {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(iter.UserData());
+ if (obj) {
+ auto appCache = static_cast<nsApplicationCache*>(obj.get());
+ appCache->MarkInvalid();
+ }
+ }
+ }
+
+ {
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ // Delete all rows whose clientID is not an active clientID.
+ nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cache WHERE rowid IN"
+ " (SELECT moz_cache.rowid FROM"
+ " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to clean up unused application caches.");
+ else
+ evictionObserver.Apply();
+
+ // Delete all namespaces whose clientID is not an active clientID.
+ rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cache_namespaces WHERE rowid IN"
+ " (SELECT moz_cache_namespaces.rowid FROM"
+ " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to clean up namespaces.");
+
+ mEvictionFunction = nullptr;
+
+ mStatement_CacheSize = nullptr;
+ mStatement_ApplicationCacheSize = nullptr;
+ mStatement_EntryCount = nullptr;
+ mStatement_UpdateEntry = nullptr;
+ mStatement_UpdateEntrySize = nullptr;
+ mStatement_DeleteEntry = nullptr;
+ mStatement_FindEntry = nullptr;
+ mStatement_BindEntry = nullptr;
+ mStatement_ClearDomain = nullptr;
+ mStatement_MarkEntry = nullptr;
+ mStatement_UnmarkEntry = nullptr;
+ mStatement_GetTypes = nullptr;
+ mStatement_FindNamespaceEntry = nullptr;
+ mStatement_InsertNamespaceEntry = nullptr;
+ mStatement_CleanupUnmarked = nullptr;
+ mStatement_GatherEntries = nullptr;
+ mStatement_ActivateClient = nullptr;
+ mStatement_DeactivateGroup = nullptr;
+ mStatement_FindClient = nullptr;
+ mStatement_FindClientByNamespace = nullptr;
+ mStatement_EnumerateApps = nullptr;
+ mStatement_EnumerateGroups = nullptr;
+ mStatement_EnumerateGroupsTimeOrder = nullptr;
+ }
+
+ // Close Database on the correct thread
+ bool isOnCurrentThread = true;
+ if (mInitThread)
+ mInitThread->IsOnCurrentThread(&isOnCurrentThread);
+
+ if (!isOnCurrentThread) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
+
+ if (ev) {
+ mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ }
+ else {
+ mDB->Close();
+ }
+
+ mDB = nullptr;
+ mInitThread = nullptr;
+
+ return NS_OK;
+}
+
+const char *
+nsOfflineCacheDevice::GetDeviceID()
+{
+ return OFFLINE_CACHE_DEVICE_ID;
+}
+
+nsCacheEntry *
+nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
+{
+ NS_ENSURE_TRUE(Initialized(), nullptr);
+
+ mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2> timer;
+ LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
+
+ // SELECT * FROM moz_cache WHERE key = ?
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
+ return nullptr;
+
+ AutoResetStatement statement(mStatement_FindEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ NS_ENSURE_SUCCESS(rv2, nullptr);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows)
+ return nullptr; // entry not found
+
+ nsOfflineCacheRecord rec;
+ statement->GetSharedBlob(0, &rec.metaDataLen,
+ (const uint8_t **) &rec.metaData);
+ rec.generation = statement->AsInt32(1);
+ rec.dataSize = statement->AsInt32(2);
+ rec.fetchCount = statement->AsInt32(3);
+ rec.lastFetched = statement->AsInt64(4);
+ rec.lastModified = statement->AsInt64(5);
+ rec.expirationTime = statement->AsInt64(6);
+
+ LOG(("entry: [%u %d %d %d %lld %lld %lld]\n",
+ rec.metaDataLen,
+ rec.generation,
+ rec.dataSize,
+ rec.fetchCount,
+ rec.lastFetched,
+ rec.lastModified,
+ rec.expirationTime));
+
+ nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
+
+ if (entry)
+ {
+ // make sure that the data file exists
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
+ bool isFile;
+ rv = binding->mDataFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile)
+ {
+ DeleteEntry(entry, false);
+ delete entry;
+ return nullptr;
+ }
+
+ // lock the entry
+ Lock(*fullKey);
+ }
+
+ return entry;
+}
+
+nsresult
+nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
+{
+ LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ // This method is called to inform us that the nsCacheEntry object is going
+ // away. We should persist anything that needs to be persisted, or if the
+ // entry is doomed, we can go ahead and clear its storage.
+
+ if (entry->IsDoomed())
+ {
+ // remove corresponding row and file if they exist
+
+ // the row should have been removed in DoomEntry... we could assert that
+ // that happened. otherwise, all we have to do here is delete the file
+ // on disk.
+ DeleteData(entry);
+ }
+ else if (((nsOfflineCacheBinding *)entry->Data())->IsNewEntry())
+ {
+ // UPDATE the database row
+
+ // Only new entries are updated, since offline cache is updated in
+ // transactions. New entries are those who is returned from
+ // BindEntry().
+
+ LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
+ UpdateEntry(entry);
+ } else {
+ LOG(("nsOfflineCacheDevice::DeactivateEntry "
+ "skipping update since entry is not dirty\n"));
+ }
+
+ // Unlock the entry
+ Unlock(*entry->Key());
+
+ delete entry;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
+
+ NS_ENSURE_STATE(!entry->Data());
+
+ // This method is called to inform us that we have a new entry. The entry
+ // may collide with an existing entry in our DB, but if that happens we can
+ // assume that the entry is not being used.
+
+ // INSERT the database row
+
+ // XXX Assumption: if the row already exists, then FindEntry would have
+ // returned it. if that entry was doomed, then DoomEntry would have removed
+ // it from the table. so, we should always have to insert at this point.
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // create binding, pick best generation number
+ RefPtr<nsOfflineCacheBinding> binding =
+ nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
+ if (!binding)
+ return NS_ERROR_OUT_OF_MEMORY;
+ binding->MarkNewEntry();
+
+ nsOfflineCacheRecord rec;
+ rec.clientID = cid;
+ rec.key = key;
+ rec.metaData = nullptr; // don't write any metadata now.
+ rec.metaDataLen = 0;
+ rec.generation = binding->mGeneration;
+ rec.dataSize = 0;
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_BindEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
+ nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(3, rec.generation);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(4, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(7, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(!hasRows, "INSERT should not result in output");
+
+ entry->SetData(binding);
+
+ // lock the entry
+ Lock(*entry->Key());
+
+ return NS_OK;
+}
+
+void
+nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
+{
+ LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
+
+ // This method is called to inform us that we should mark the entry to be
+ // deleted when it is no longer in use.
+
+ // We can go ahead and delete the corresponding row in our table,
+ // but we must not delete the file on disk until we are deactivated.
+ // In another word, the file should be deleted if the entry had been
+ // deactivated.
+
+ DeleteEntry(entry, !entry->IsActive());
+}
+
+nsresult
+nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream **result)
+{
+ LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
+
+ // return an input stream to the entry's data file. the stream
+ // may be read on a background thread.
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIInputStream> in;
+ NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
+ if (!in)
+ return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ if (offset != 0)
+ {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ }
+
+ in.swap(*result);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream **result)
+{
+ LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
+
+ // return an output stream to the entry's data file. we can assume
+ // that the output stream will only be used on the main thread.
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIOutputStream> out;
+ NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ 00600);
+ if (!out)
+ return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+ if (offset != 0)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+
+ // truncate the file at the given offset
+ seekable->SetEOF();
+
+ nsCOMPtr<nsIOutputStream> bufferedOut;
+ nsresult rv =
+ NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bufferedOut.swap(*result);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
+{
+ LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ NS_IF_ADDREF(*result = binding->mDataFile);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, int32_t deltaSize)
+{
+ LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
+ entry->Key()->get(), deltaSize));
+
+ const int32_t DELTA_THRESHOLD = 1<<14; // 16k
+
+ // called to notify us of an impending change in the total size of the
+ // specified entry.
+
+ uint32_t oldSize = entry->DataSize();
+ NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
+ uint32_t newSize = int32_t(oldSize) + deltaSize;
+ UpdateEntrySize(entry, newSize);
+
+ mDeltaCounter += deltaSize; // this may go negative
+
+ if (mDeltaCounter >= DELTA_THRESHOLD)
+ {
+ if (CacheSize() > mCacheCapacity) {
+ // the entry will overrun the cache capacity, doom the entry
+ // and abort
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+
+ mDeltaCounter = 0; // reset counter
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // called to enumerate the offline cache.
+
+ nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
+ new nsOfflineCacheDeviceInfo(this);
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
+ &keepGoing);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!keepGoing)
+ return NS_OK;
+
+ // SELECT * from moz_cache;
+
+ nsOfflineCacheRecord rec;
+ RefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
+ if (!info)
+ return NS_ERROR_OUT_OF_MEMORY;
+ info->mRec = &rec;
+
+ // XXX may want to list columns explicitly
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ for (;;)
+ {
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows)
+ break;
+
+ statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
+ statement->GetSharedUTF8String(1, nullptr, &rec.key);
+ statement->GetSharedBlob(2, &rec.metaDataLen,
+ (const uint8_t **) &rec.metaData);
+ rec.generation = statement->AsInt32(3);
+ rec.dataSize = statement->AsInt32(4);
+ rec.fetchCount = statement->AsInt32(5);
+ rec.lastFetched = statement->AsInt64(6);
+ rec.lastModified = statement->AsInt64(7);
+ rec.expirationTime = statement->AsInt64(8);
+
+ bool keepGoing;
+ rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
+ if (NS_FAILED(rv) || !keepGoing)
+ break;
+ }
+
+ info->mRec = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::EvictEntries(const char *clientID)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
+ clientID ? clientID : ""));
+
+ // called to evict all entries matching the given clientID.
+
+ // need trigger to fire user defined function after a row is deleted
+ // so we can delete the corresponding data file.
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv;
+ if (clientID)
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO - Should update internal hashtables.
+ // Low priority, since this API is not widely used.
+ }
+ else
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Clear();
+ mActiveCaches.Clear();
+ mActiveCachesByGroup.Clear();
+ }
+
+ evictionObserver.Apply();
+
+ statement = nullptr;
+ // Also evict any namespaces associated with this clientID.
+ if (clientID)
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_MarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_UnmarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove the entry if it is now empty.
+
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
+ rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cleanupStatement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cleanupStatement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ evictionObserver.Apply();
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
+ const nsACString &key,
+ nsIApplicationCacheNamespace **out)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
+ clientID.get(), PromiseFlatCString(key).get()));
+
+ nsresult rv;
+
+ AutoResetStatement statement(mStatement_FindNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *out = nullptr;
+
+ bool found = false;
+ nsCString nsSpec;
+ int32_t nsType = 0;
+ nsCString nsData;
+
+ while (hasRows)
+ {
+ int32_t itemType;
+ rv = statement->GetInt32(2, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!found || itemType > nsType)
+ {
+ nsType = itemType;
+
+ rv = statement->GetUTF8String(0, nsSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->GetUTF8String(1, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ found = true;
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (found) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ new nsApplicationCacheNamespace();
+ if (!ns)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = ns->Init(nsType, nsSpec, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ns.swap(*out);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
+ const nsACString &key)
+{
+ // XXX: We should also be propagating this cache entry to other matching
+ // caches. See bug 444807.
+
+ return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
+}
+
+nsresult
+nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t *typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
+ clientID.get(), PromiseFlatCString(key).get()));
+
+ AutoResetStatement statement(mStatement_GetTypes);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows)
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+
+ *typeBits = statement->AsInt32(0);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
+ uint32_t typeBits,
+ uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
+ clientID.get(), typeBits));
+
+ AutoResetStatement statement(mStatement_GatherEntries);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(1, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
+}
+
+nsresult
+nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
+ nsIApplicationCacheNamespace *ns)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString namespaceSpec;
+ nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = ns->GetData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t itemType;
+ rv = ns->GetItemType(&itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
+ clientID.get(), namespaceSpec.get(), data.get(), itemType));
+
+ AutoResetStatement statement(mStatement_InsertNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(2, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(3, itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
+ uint32_t *usage)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
+ PromiseFlatCString(clientID).get()));
+
+ *usage = 0;
+
+ AutoResetStatement statement(mStatement_ApplicationCacheSize);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows)
+ return NS_OK;
+
+ *usage = static_cast<uint32_t>(statement->AsInt32(0));
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetGroups(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroups"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
+}
+
+nsresult
+nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
+}
+
+bool
+nsOfflineCacheDevice::IsLocked(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ return mLockedEntries.GetEntry(key);
+}
+
+void
+nsOfflineCacheDevice::Lock(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ mLockedEntries.PutEntry(key);
+}
+
+void
+nsOfflineCacheDevice::Unlock(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ mLockedEntries.RemoveEntry(key);
+}
+
+nsresult
+nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
+ uint32_t resultIndex,
+ uint32_t * count,
+ char *** values)
+{
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> valArray;
+ while (hasRows)
+ {
+ uint32_t length;
+ valArray.AppendElement(
+ nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *count = valArray.Length();
+ char **ret = static_cast<char **>(moz_xmalloc(*count * sizeof(char*)));
+ if (!ret) return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t i = 0; i < *count; i++) {
+ ret[i] = NS_strdup(valArray[i].get());
+ if (!ret[i]) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ *values = ret;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ nsCString clientID;
+ // Some characters are special in the clientID. Escape the groupID
+ // before putting it in to the client key.
+ if (!NS_Escape(nsCString(group), clientID, url_Path)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ PRTime now = PR_Now();
+
+ // Include the timestamp to guarantee uniqueness across runs, and
+ // the gNextTemporaryClientID for uniqueness within a second.
+ clientID.Append(nsPrintfCString("|%016lld|%d",
+ now / PR_USEC_PER_SEC,
+ gNextTemporaryClientID++));
+
+ nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
+ group,
+ clientID);
+ if (!cache)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
+ if (!weak)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ MutexAutoLock lock(mLock);
+ mCaches.Put(clientID, weak);
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
+ nsIApplicationCache **out)
+{
+ MutexAutoLock lock(mLock);
+ return GetApplicationCache_Unlocked(clientID, out);
+}
+
+nsresult
+nsOfflineCacheDevice::GetApplicationCache_Unlocked(const nsACString &clientID,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ nsCOMPtr<nsIApplicationCache> cache;
+
+ nsWeakPtr weak;
+ if (mCaches.Get(clientID, getter_AddRefs(weak)))
+ cache = do_QueryReferent(weak);
+
+ if (!cache)
+ {
+ nsCString group;
+ nsresult rv = GetGroupForCache(clientID, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (group.IsEmpty()) {
+ return NS_OK;
+ }
+
+ cache = new nsApplicationCache(this, group, clientID);
+ weak = do_GetWeakReference(cache);
+ if (!weak)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mCaches.Put(clientID, weak);
+ }
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ MutexAutoLock lock(mLock);
+
+ nsCString *clientID;
+ if (mActiveCachesByGroup.Get(group, &clientID))
+ return GetApplicationCache_Unlocked(*clientID, out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString *active = nullptr;
+
+ AutoResetStatement statement(mStatement_DeactivateGroup);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mActiveCachesByGroup.Get(group, &active))
+ {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Evict(nsILoadContextInfo *aInfo)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aInfo);
+
+ nsresult rv;
+
+ mozilla::OriginAttributes const *oa = aInfo->OriginAttributesPtr();
+
+ if (oa->mAppId == NECKO_NO_APP_ID && oa->mInIsolatedMozBrowser == false) {
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsCacheService::GlobalInstance()->EvictEntriesInternal(nsICache::STORE_OFFLINE);
+ }
+
+ nsAutoCString jaridsuffix;
+ jaridsuffix.Append('%');
+
+ nsAutoCString suffix;
+ oa->CreateSuffix(suffix);
+ jaridsuffix.Append('#');
+ jaridsuffix.Append(suffix);
+
+ AutoResetStatement statement(mStatement_EnumerateApps);
+ rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+namespace { // anon
+
+class OriginMatch final : public mozIStorageFunction
+{
+ ~OriginMatch() {}
+ mozilla::OriginAttributesPattern const mPattern;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+ explicit OriginMatch(mozilla::OriginAttributesPattern const &aPattern)
+ : mPattern(aPattern) {}
+};
+
+NS_IMPL_ISUPPORTS(OriginMatch, mozIStorageFunction)
+
+NS_IMETHODIMP
+OriginMatch::OnFunctionCall(mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString groupId;
+ rv = aFunctionArguments->GetUTF8String(0, groupId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t hash = groupId.Find(NS_LITERAL_CSTRING("#"));
+ if (hash == kNotFound) {
+ // Just ignore...
+ return NS_OK;
+ }
+
+ ++hash;
+
+ nsDependentCSubstring suffix(groupId.BeginReading() + hash, groupId.Length() - hash);
+
+ mozilla::NeckoOriginAttributes oa;
+ bool ok = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+
+ bool match = mPattern.Matches(oa);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsUint32(match ? 1 : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // anon
+
+nsresult
+nsOfflineCacheDevice::Evict(mozilla::OriginAttributesPattern const &aPattern)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ nsCOMPtr<mozIStorageFunction> function1(new OriginMatch(aPattern));
+ rv = mDB->CreateFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"), 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ class AutoRemoveFunc {
+ public:
+ mozIStorageConnection* mDB;
+ explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
+ ~AutoRemoveFunc() {
+ mDB->RemoveFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"));
+ }
+ };
+ AutoRemoveFunc autoRemove(mDB);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE ORIGIN_MATCH(GroupID);"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoResetStatement statementScope(statement);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
+ const nsACString &clientID,
+ nsILoadContextInfo *loadContextInfo)
+{
+ {
+ MutexAutoLock lock(mLock);
+ if (!mActiveCaches.Contains(clientID))
+ return false;
+ }
+
+ nsAutoCString groupID;
+ nsresult rv = GetGroupForCache(clientID, groupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> groupURI;
+ rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // When we are choosing an initial cache to load the top
+ // level document from, the URL of that document must have
+ // the same origin as the manifest, according to the spec.
+ // The following check is here because explicit, fallback
+ // and dynamic entries might have origin different from the
+ // manifest origin.
+ if (!NS_SecurityCompareURIs(keyURI, groupURI,
+ GetStrictFileOriginPolicy())) {
+ return false;
+ }
+
+ // Check the groupID we found is equal to groupID based
+ // on the load context demanding load from app cache.
+ // This is check of extended origin.
+
+ nsAutoCString originSuffix;
+ loadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
+
+ nsAutoCString demandedGroupID;
+ rv = BuildApplicationCacheGroupID(groupURI, originSuffix, demandedGroupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (groupID != demandedGroupID) {
+ return false;
+ }
+
+ return true;
+}
+
+
+nsresult
+nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
+ nsILoadContextInfo *loadContextInfo,
+ nsIApplicationCache **out)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(loadContextInfo);
+
+ nsresult rv;
+
+ *out = nullptr;
+
+ nsCOMPtr<nsIURI> keyURI;
+ rv = NS_NewURI(getter_AddRefs(keyURI), key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // First try to find a matching cache entry.
+ AutoResetStatement statement(mStatement_FindClient);
+ rv = statement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ int32_t itemType;
+ rv = statement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
+ nsAutoCString clientID;
+ rv = statement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // OK, we didn't find an exact match. Search for a client with a
+ // matching namespace.
+
+ AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
+
+ rv = nsstatement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows)
+ {
+ int32_t itemType;
+ rv = nsstatement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't associate with a cache based solely on a whitelist entry
+ if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
+ nsAutoCString clientID;
+ rv = nsstatement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString &key)
+{
+ NS_ENSURE_ARG_POINTER(cache);
+
+ nsresult rv;
+
+ nsAutoCString clientID;
+ rv = cache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CacheOpportunistically(clientID, key);
+}
+
+nsresult
+nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
+ const nsCSubstring &clientID)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ AutoResetStatement statement(mStatement_ActivateClient);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ nsCString *active;
+ if (mActiveCachesByGroup.Get(group, &active))
+ {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ if (!clientID.IsEmpty())
+ {
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+ }
+
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
+ const nsCSubstring &clientID)
+{
+ nsCString *active = nullptr;
+ MutexAutoLock lock(mLock);
+ return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
+}
+
+/**
+ * Preference accessors
+ */
+
+void
+nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
+{
+ if (Initialized())
+ {
+ NS_ERROR("cannot switch cache directory once initialized");
+ return;
+ }
+
+ if (!parentDir)
+ {
+ mCacheDirectory = nullptr;
+ return;
+ }
+
+ // ensure parent directory exists
+ nsresult rv = EnsureDir(parentDir);
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("unable to create parent directory");
+ return;
+ }
+
+ mBaseDirectory = parentDir;
+
+ // cache dir may not exist, but that's ok
+ nsCOMPtr<nsIFile> dir;
+ rv = parentDir->Clone(getter_AddRefs(dir));
+ if (NS_FAILED(rv))
+ return;
+ rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ mCacheDirectory = do_QueryInterface(dir);
+}
+
+void
+nsOfflineCacheDevice::SetCapacity(uint32_t capacity)
+{
+ mCacheCapacity = capacity * 1024;
+}
+
+bool
+nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache)
+{
+ if (!mAutoShutdown)
+ return false;
+
+ mAutoShutdown = false;
+
+ Shutdown();
+
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
+ RefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
+ cacheService->RemoveCustomOfflineDevice(this);
+
+ nsAutoCString clientID;
+ aAppCache->GetClientID(clientID);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Remove(clientID);
+
+ return true;
+}
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.h b/netwerk/cache/nsDiskCacheDeviceSQL.h
new file mode 100644
index 000000000..fcde58d3d
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -0,0 +1,290 @@
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOfflineCacheDevice_h__
+#define nsOfflineCacheDevice_h__
+
+#include "nsCacheDevice.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIObserver.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageFunction.h"
+#include "nsIFile.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+class nsIURI;
+class nsOfflineCacheDevice;
+class mozIStorageService;
+class nsILoadContextInfo;
+namespace mozilla { class OriginAttributesPattern; }
+
+class nsApplicationCacheNamespace final : public nsIApplicationCacheNamespace
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHENAMESPACE
+
+ nsApplicationCacheNamespace() : mItemType(0) {}
+
+private:
+ ~nsApplicationCacheNamespace() {}
+
+ uint32_t mItemType;
+ nsCString mNamespaceSpec;
+ nsCString mData;
+};
+
+class nsOfflineCacheEvictionFunction final : public mozIStorageFunction {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ explicit nsOfflineCacheEvictionFunction(nsOfflineCacheDevice *device);
+
+ void Init();
+ void Reset();
+ void Apply();
+
+private:
+ ~nsOfflineCacheEvictionFunction() {}
+
+ nsOfflineCacheDevice *mDevice;
+ bool mTLSInited;
+};
+
+class nsOfflineCacheDevice final : public nsCacheDevice
+ , public nsISupports
+{
+public:
+ nsOfflineCacheDevice();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * nsCacheDevice methods
+ */
+
+ virtual nsresult Init() override;
+ nsresult InitWithSqlite(mozIStorageService * ss);
+ virtual nsresult Shutdown() override;
+
+ virtual const char * GetDeviceID(void) override;
+ virtual nsCacheEntry * FindEntry(nsCString * key, bool *collision) override;
+ virtual nsresult DeactivateEntry(nsCacheEntry * entry) override;
+ virtual nsresult BindEntry(nsCacheEntry * entry) override;
+ virtual void DoomEntry( nsCacheEntry * entry ) override;
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result) override;
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result) override;
+
+ virtual nsresult GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result) override;
+
+ virtual nsresult OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize) override;
+
+ virtual nsresult Visit(nsICacheVisitor * visitor) override;
+
+ virtual nsresult EvictEntries(const char * clientID) override;
+
+ /* Entry ownership */
+ nsresult GetOwnerDomains(const char * clientID,
+ uint32_t * count,
+ char *** domains);
+ nsresult GetOwnerURIs(const char * clientID,
+ const nsACString & ownerDomain,
+ uint32_t * count,
+ char *** uris);
+ nsresult SetOwnedKeys(const char * clientID,
+ const nsACString & ownerDomain,
+ const nsACString & ownerUrl,
+ uint32_t count,
+ const char ** keys);
+ nsresult GetOwnedKeys(const char * clientID,
+ const nsACString & ownerDomain,
+ const nsACString & ownerUrl,
+ uint32_t * count,
+ char *** keys);
+ nsresult AddOwnedKey(const char * clientID,
+ const nsACString & ownerDomain,
+ const nsACString & ownerURI,
+ const nsACString & key);
+ nsresult RemoveOwnedKey(const char * clientID,
+ const nsACString & ownerDomain,
+ const nsACString & ownerURI,
+ const nsACString & key);
+ nsresult KeyIsOwned(const char * clientID,
+ const nsACString & ownerDomain,
+ const nsACString & ownerURI,
+ const nsACString & key,
+ bool * isOwned);
+
+ nsresult ClearKeysOwnedByDomain(const char *clientID,
+ const nsACString &ownerDomain);
+ nsresult EvictUnownedEntries(const char *clientID);
+
+ static nsresult BuildApplicationCacheGroupID(nsIURI *aManifestURL,
+ nsACString const &aOriginSuffix,
+ nsACString &_result);
+
+ nsresult ActivateCache(const nsCSubstring &group,
+ const nsCSubstring &clientID);
+ bool IsActiveCache(const nsCSubstring &group,
+ const nsCSubstring &clientID);
+ nsresult CreateApplicationCache(const nsACString &group,
+ nsIApplicationCache **out);
+
+ nsresult GetApplicationCache(const nsACString &clientID,
+ nsIApplicationCache **out);
+ nsresult GetApplicationCache_Unlocked(const nsACString &clientID,
+ nsIApplicationCache **out);
+
+ nsresult GetActiveCache(const nsACString &group,
+ nsIApplicationCache **out);
+
+ nsresult DeactivateGroup(const nsACString &group);
+
+ nsresult ChooseApplicationCache(const nsACString &key,
+ nsILoadContextInfo *loadContext,
+ nsIApplicationCache **out);
+
+ nsresult CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString &key);
+
+ nsresult Evict(nsILoadContextInfo *aInfo);
+ nsresult Evict(mozilla::OriginAttributesPattern const &aPattern);
+
+ nsresult GetGroups(uint32_t *count,char ***keys);
+
+ nsresult GetGroupsTimeOrdered(uint32_t *count,
+ char ***keys);
+
+ bool IsLocked(const nsACString &key);
+ void Lock(const nsACString &key);
+ void Unlock(const nsACString &key);
+
+ /**
+ * Preference accessors
+ */
+
+ void SetCacheParentDirectory(nsIFile * parentDir);
+ void SetCapacity(uint32_t capacity);
+ void SetAutoShutdown() { mAutoShutdown = true; }
+ bool AutoShutdown(nsIApplicationCache * aAppCache);
+
+ nsIFile * BaseDirectory() { return mBaseDirectory; }
+ nsIFile * CacheDirectory() { return mCacheDirectory; }
+ uint32_t CacheCapacity() { return mCacheCapacity; }
+ uint32_t CacheSize();
+ uint32_t EntryCount();
+
+private:
+ ~nsOfflineCacheDevice();
+
+ friend class nsApplicationCache;
+
+ static bool GetStrictFileOriginPolicy();
+
+ bool Initialized() { return mDB != nullptr; }
+
+ nsresult InitActiveCaches();
+ nsresult UpdateEntry(nsCacheEntry *entry);
+ nsresult UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize);
+ nsresult DeleteEntry(nsCacheEntry *entry, bool deleteData);
+ nsresult DeleteData(nsCacheEntry *entry);
+ nsresult EnableEvictionObserver();
+ nsresult DisableEvictionObserver();
+
+ bool CanUseCache(nsIURI *keyURI, const nsACString &clientID, nsILoadContextInfo *loadContext);
+
+ nsresult MarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits);
+ nsresult UnmarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits);
+
+ nsresult CacheOpportunistically(const nsCString &clientID,
+ const nsACString &key);
+ nsresult GetTypes(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t *typeBits);
+
+ nsresult GetMatchingNamespace(const nsCString &clientID,
+ const nsACString &key,
+ nsIApplicationCacheNamespace **out);
+ nsresult GatherEntries(const nsCString &clientID,
+ uint32_t typeBits,
+ uint32_t *count,
+ char *** values);
+ nsresult AddNamespace(const nsCString &clientID,
+ nsIApplicationCacheNamespace *ns);
+
+ nsresult GetUsage(const nsACString &clientID,
+ uint32_t *usage);
+
+ nsresult RunSimpleQuery(mozIStorageStatement *statment,
+ uint32_t resultIndex,
+ uint32_t * count,
+ char *** values);
+
+ nsCOMPtr<mozIStorageConnection> mDB;
+ RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+
+ nsCOMPtr<mozIStorageStatement> mStatement_CacheSize;
+ nsCOMPtr<mozIStorageStatement> mStatement_ApplicationCacheSize;
+ nsCOMPtr<mozIStorageStatement> mStatement_EntryCount;
+ nsCOMPtr<mozIStorageStatement> mStatement_UpdateEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_UpdateEntrySize;
+ nsCOMPtr<mozIStorageStatement> mStatement_DeleteEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_BindEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_ClearDomain;
+ nsCOMPtr<mozIStorageStatement> mStatement_MarkEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_UnmarkEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_GetTypes;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindNamespaceEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_InsertNamespaceEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_CleanupUnmarked;
+ nsCOMPtr<mozIStorageStatement> mStatement_GatherEntries;
+ nsCOMPtr<mozIStorageStatement> mStatement_ActivateClient;
+ nsCOMPtr<mozIStorageStatement> mStatement_DeactivateGroup;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindClient;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindClientByNamespace;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateApps;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateGroups;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateGroupsTimeOrder;
+
+ nsCOMPtr<nsIFile> mBaseDirectory;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ uint32_t mCacheCapacity; // in bytes
+ int32_t mDeltaCounter;
+ bool mAutoShutdown;
+
+ mozilla::Mutex mLock;
+
+ nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
+ nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
+ nsTHashtable<nsCStringHashKey> mActiveCaches;
+ nsTHashtable<nsCStringHashKey> mLockedEntries;
+
+ nsCOMPtr<nsIThread> mInitThread;
+};
+
+#endif // nsOfflineCacheDevice_h__
diff --git a/netwerk/cache/nsDiskCacheEntry.cpp b/netwerk/cache/nsDiskCacheEntry.cpp
new file mode 100644
index 000000000..cd5a34977
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheEntry.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheBinding.h"
+#include "nsCRT.h"
+
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+
+/******************************************************************************
+ * nsDiskCacheEntry
+ *****************************************************************************/
+
+/**
+ * CreateCacheEntry()
+ *
+ * Creates an nsCacheEntry and sets all fields except for the binding.
+ */
+nsCacheEntry *
+nsDiskCacheEntry::CreateCacheEntry(nsCacheDevice * device)
+{
+ nsCacheEntry * entry = nullptr;
+ nsresult rv = nsCacheEntry::Create(Key(),
+ nsICache::STREAM_BASED,
+ nsICache::STORE_ON_DISK,
+ device,
+ &entry);
+ if (NS_FAILED(rv) || !entry) return nullptr;
+
+ entry->SetFetchCount(mFetchCount);
+ entry->SetLastFetched(mLastFetched);
+ entry->SetLastModified(mLastModified);
+ entry->SetExpirationTime(mExpirationTime);
+ entry->SetCacheDevice(device);
+ // XXX why does nsCacheService have to fill out device in BindEntry()?
+ entry->SetDataSize(mDataSize);
+
+ rv = entry->UnflattenMetaData(MetaData(), mMetaDataSize);
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+
+ // Restore security info, if present
+ const char* info = entry->GetMetaDataElement("security-info");
+ if (info) {
+ nsCOMPtr<nsISupports> infoObj;
+ rv = NS_DeserializeObject(nsDependentCString(info),
+ getter_AddRefs(infoObj));
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetSecurityInfo(infoObj);
+ }
+
+ return entry;
+}
+
+
+/******************************************************************************
+ * nsDiskCacheEntryInfo
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsDiskCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetClientID(char ** clientID)
+{
+ NS_ENSURE_ARG_POINTER(clientID);
+ return ClientIDFromCacheKey(nsDependentCString(mDiskEntry->Key()), clientID);
+}
+
+extern const char DISK_CACHE_DEVICE_ID[];
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ NS_ENSURE_ARG_POINTER(deviceID);
+ *deviceID = NS_strdup(mDeviceID);
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetKey(nsACString &clientKey)
+{
+ return ClientKeyFromCacheKey(nsDependentCString(mDiskEntry->Key()), clientKey);
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
+{
+ NS_ENSURE_ARG_POINTER(aFetchCount);
+ *aFetchCount = mDiskEntry->mFetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
+{
+ NS_ENSURE_ARG_POINTER(aLastFetched);
+ *aLastFetched = mDiskEntry->mLastFetched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
+{
+ NS_ENSURE_ARG_POINTER(aLastModified);
+ *aLastModified = mDiskEntry->mLastModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
+{
+ NS_ENSURE_ARG_POINTER(aExpirationTime);
+ *aExpirationTime = mDiskEntry->mExpirationTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::IsStreamBased(bool *aStreamBased)
+{
+ NS_ENSURE_ARG_POINTER(aStreamBased);
+ *aStreamBased = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDiskCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
+{
+ NS_ENSURE_ARG_POINTER(aDataSize);
+ *aDataSize = mDiskEntry->mDataSize;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheEntry.h b/netwerk/cache/nsDiskCacheEntry.h
new file mode 100644
index 000000000..3bec33053
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheEntry.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsDiskCacheEntry_h_
+#define _nsDiskCacheEntry_h_
+
+#include "nsDiskCacheMap.h"
+
+#include "nsCacheEntry.h"
+
+
+/******************************************************************************
+ * nsDiskCacheEntry
+ *****************************************************************************/
+struct nsDiskCacheEntry {
+ uint32_t mHeaderVersion; // useful for stand-alone metadata files
+ uint32_t mMetaLocation; // for verification
+ int32_t mFetchCount;
+ uint32_t mLastFetched;
+ uint32_t mLastModified;
+ uint32_t mExpirationTime;
+ uint32_t mDataSize;
+ uint32_t mKeySize; // includes terminating null byte
+ uint32_t mMetaDataSize; // includes terminating null byte
+ // followed by key data (mKeySize bytes)
+ // followed by meta data (mMetaDataSize bytes)
+
+ uint32_t Size() { return sizeof(nsDiskCacheEntry) +
+ mKeySize + mMetaDataSize;
+ }
+
+ char* Key() { return reinterpret_cast<char*const>(this) +
+ sizeof(nsDiskCacheEntry);
+ }
+
+ char* MetaData()
+ { return Key() + mKeySize; }
+
+ nsCacheEntry * CreateCacheEntry(nsCacheDevice * device);
+
+ void Swap() // host to network (memory to disk)
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mHeaderVersion = htonl(mHeaderVersion);
+ mMetaLocation = htonl(mMetaLocation);
+ mFetchCount = htonl(mFetchCount);
+ mLastFetched = htonl(mLastFetched);
+ mLastModified = htonl(mLastModified);
+ mExpirationTime = htonl(mExpirationTime);
+ mDataSize = htonl(mDataSize);
+ mKeySize = htonl(mKeySize);
+ mMetaDataSize = htonl(mMetaDataSize);
+#endif
+ }
+
+ void Unswap() // network to host (disk to memory)
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mHeaderVersion = ntohl(mHeaderVersion);
+ mMetaLocation = ntohl(mMetaLocation);
+ mFetchCount = ntohl(mFetchCount);
+ mLastFetched = ntohl(mLastFetched);
+ mLastModified = ntohl(mLastModified);
+ mExpirationTime = ntohl(mExpirationTime);
+ mDataSize = ntohl(mDataSize);
+ mKeySize = ntohl(mKeySize);
+ mMetaDataSize = ntohl(mMetaDataSize);
+#endif
+ }
+};
+
+
+/******************************************************************************
+ * nsDiskCacheEntryInfo
+ *****************************************************************************/
+class nsDiskCacheEntryInfo : public nsICacheEntryInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsDiskCacheEntryInfo(const char * deviceID, nsDiskCacheEntry * diskEntry)
+ : mDeviceID(deviceID)
+ , mDiskEntry(diskEntry)
+ {
+ }
+
+ const char* Key() { return mDiskEntry->Key(); }
+
+private:
+ virtual ~nsDiskCacheEntryInfo() {}
+
+ const char * mDeviceID;
+ nsDiskCacheEntry * mDiskEntry;
+};
+
+
+#endif /* _nsDiskCacheEntry_h_ */
diff --git a/netwerk/cache/nsDiskCacheMap.cpp b/netwerk/cache/nsDiskCacheMap.cpp
new file mode 100644
index 000000000..d7ce13a35
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheMap.cpp
@@ -0,0 +1,1430 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsDiskCacheMap.h"
+#include "nsDiskCacheBinding.h"
+#include "nsDiskCacheEntry.h"
+#include "nsDiskCacheDevice.h"
+#include "nsCacheService.h"
+
+#include <string.h>
+#include "nsPrintfCString.h"
+
+#include "nsISerializable.h"
+#include "nsSerializationHelper.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+/******************************************************************************
+ * nsDiskCacheMap
+ *****************************************************************************/
+
+/**
+ * File operations
+ */
+
+nsresult
+nsDiskCacheMap::Open(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+
+ // Assume we have an unexpected error until we find otherwise.
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+ NS_ENSURE_ARG_POINTER(cacheDirectory);
+ if (mMapFD) return NS_ERROR_ALREADY_INITIALIZED;
+
+ mCacheDirectory = cacheDirectory; // save a reference for ourselves
+
+ // create nsIFile for _CACHE_MAP_
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ rv = cacheDirectory->Clone(getter_AddRefs(file));
+ rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // open the file - restricted to user, the data could be confidential
+ rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kOpenCacheMapError;
+ NS_WARNING("Could not open cache map file");
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ bool cacheFilesExist = CacheFilesExist();
+ rv = NS_ERROR_FILE_CORRUPTED; // presume the worst
+ uint32_t mapSize = PR_Available(mMapFD);
+
+ if (NS_FAILED(InitCacheClean(cacheDirectory,
+ corruptInfo))) {
+ // corruptInfo is set in the call to InitCacheClean
+ goto error_exit;
+ }
+
+ // check size of map file
+ if (mapSize == 0) { // creating a new _CACHE_MAP_
+
+ // block files shouldn't exist if we're creating the _CACHE_MAP_
+ if (cacheFilesExist) {
+ *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
+ goto error_exit;
+ }
+
+ if (NS_FAILED(CreateCacheSubDirectories())) {
+ *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
+ goto error_exit;
+ }
+
+ // create the file - initialize in memory
+ memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
+ mHeader.mVersion = nsDiskCache::kCurrentVersion;
+ mHeader.mRecordCount = kMinRecordCount;
+ mRecordArray = (nsDiskCacheRecord *)
+ PR_CALLOC(mHeader.mRecordCount * sizeof(nsDiskCacheRecord));
+ if (!mRecordArray) {
+ *corruptInfo = nsDiskCache::kOutOfMemory;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto error_exit;
+ }
+ } else if (mapSize >= sizeof(nsDiskCacheHeader)) { // read existing _CACHE_MAP_
+
+ // if _CACHE_MAP_ exists, so should the block files
+ if (!cacheFilesExist) {
+ *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
+ goto error_exit;
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
+
+ // read the header
+ uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
+ if (sizeof(nsDiskCacheHeader) != bytesRead) {
+ *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
+ goto error_exit;
+ }
+ mHeader.Unswap();
+
+ if (mHeader.mIsDirty) {
+ *corruptInfo = nsDiskCache::kHeaderIsDirty;
+ goto error_exit;
+ }
+
+ if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
+ *corruptInfo = nsDiskCache::kVersionMismatch;
+ goto error_exit;
+ }
+
+ uint32_t recordArraySize =
+ mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
+ if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
+ *corruptInfo = nsDiskCache::kRecordsIncomplete;
+ goto error_exit;
+ }
+
+ // Get the space for the records
+ mRecordArray = (nsDiskCacheRecord *) PR_MALLOC(recordArraySize);
+ if (!mRecordArray) {
+ *corruptInfo = nsDiskCache::kOutOfMemory;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto error_exit;
+ }
+
+ // Read the records
+ bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
+ if (bytesRead < recordArraySize) {
+ *corruptInfo = nsDiskCache::kNotEnoughToRead;
+ goto error_exit;
+ }
+
+ // Unswap each record
+ int32_t total = 0;
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber()) {
+#if defined(IS_LITTLE_ENDIAN)
+ mRecordArray[i].Unswap();
+#endif
+ total ++;
+ }
+ }
+
+ // verify entry count
+ if (total != mHeader.mEntryCount) {
+ *corruptInfo = nsDiskCache::kEntryCountIncorrect;
+ goto error_exit;
+ }
+
+ } else {
+ *corruptInfo = nsDiskCache::kHeaderIncomplete;
+ goto error_exit;
+ }
+
+ rv = OpenBlockFiles(corruptInfo);
+ if (NS_FAILED(rv)) {
+ // corruptInfo is set in the call to OpenBlockFiles
+ goto error_exit;
+ }
+
+ // set dirty bit and flush header
+ mHeader.mIsDirty = true;
+ rv = FlushHeader();
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kFlushHeaderError;
+ goto error_exit;
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD,
+ (uint32_t)SizeOfExcludingThis(moz_malloc_size_of));
+
+ *corruptInfo = nsDiskCache::kNotCorrupt;
+ return NS_OK;
+
+error_exit:
+ (void) Close(false);
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::Close(bool flush)
+{
+ nsCacheService::AssertOwnsLock();
+ nsresult rv = NS_OK;
+
+ // Cancel any pending cache validation event, the FlushRecords call below
+ // will validate the cache.
+ if (mCleanCacheTimer) {
+ mCleanCacheTimer->Cancel();
+ }
+
+ // If cache map file and its block files are still open, close them
+ if (mMapFD) {
+ // close block files
+ rv = CloseBlockFiles(flush);
+ if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
+ // write the map records
+ rv = FlushRecords(false); // don't bother swapping buckets back
+ if (NS_SUCCEEDED(rv)) {
+ // clear dirty bit
+ mHeader.mIsDirty = false;
+ rv = FlushHeader();
+ }
+ }
+ if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
+ rv = NS_ERROR_UNEXPECTED;
+
+ mMapFD = nullptr;
+ }
+
+ if (mCleanFD) {
+ PR_Close(mCleanFD);
+ mCleanFD = nullptr;
+ }
+
+ PR_FREEIF(mRecordArray);
+ PR_FREEIF(mBuffer);
+ mBufferSize = 0;
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::Trim()
+{
+ nsresult rv, rv2 = NS_OK;
+ for (int i=0; i < kNumBlockFiles; ++i) {
+ rv = mBlockFile[i].Trim();
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ }
+ // Try to shrink the records array
+ rv = ShrinkRecords();
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ return rv2;
+}
+
+
+nsresult
+nsDiskCacheMap::FlushHeader()
+{
+ if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to beginning of cache map
+ int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
+ if (filePos != 0) return NS_ERROR_UNEXPECTED;
+
+ // write the header
+ mHeader.Swap();
+ int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
+ mHeader.Unswap();
+ if (sizeof(nsDiskCacheHeader) != bytesWritten) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PRStatus err = PR_Sync(mMapFD);
+ if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
+
+ // If we have a clean header then revalidate the cache clean file
+ if (!mHeader.mIsDirty) {
+ RevalidateCache();
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheMap::FlushRecords(bool unswap)
+{
+ if (!mMapFD) return NS_ERROR_NOT_AVAILABLE;
+
+ // seek to beginning of buckets
+ int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
+ if (filePos != sizeof(nsDiskCacheHeader))
+ return NS_ERROR_UNEXPECTED;
+
+#if defined(IS_LITTLE_ENDIAN)
+ // Swap each record
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber())
+ mRecordArray[i].Swap();
+ }
+#endif
+
+ int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
+
+ int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
+ if (bytesWritten != recordArraySize)
+ return NS_ERROR_UNEXPECTED;
+
+#if defined(IS_LITTLE_ENDIAN)
+ if (unswap) {
+ // Unswap each record
+ for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
+ if (mRecordArray[i].HashNumber())
+ mRecordArray[i].Unswap();
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+
+/**
+ * Record operations
+ */
+
+uint32_t
+nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
+{
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ uint32_t rank = 0;
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if ((rank < records[i].EvictionRank()) &&
+ ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
+ rank = records[i].EvictionRank();
+ }
+ return rank;
+}
+
+nsresult
+nsDiskCacheMap::GrowRecords()
+{
+ if (mHeader.mRecordCount >= mMaxRecordCount)
+ return NS_OK;
+ CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
+
+ // Resize the record array
+ int32_t newCount = mHeader.mRecordCount << 1;
+ if (newCount > mMaxRecordCount)
+ newCount = mMaxRecordCount;
+ nsDiskCacheRecord *newArray = (nsDiskCacheRecord *)
+ PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
+ if (!newArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Space out the buckets
+ uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
+ uint32_t newRecordsPerBucket = newCount / kBuckets;
+ // Work from back to space out each bucket to the new array
+ for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
+ // Move bucket
+ nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
+ const uint32_t count = mHeader.mBucketUsage[bucketIndex];
+ memmove(newRecords,
+ newArray + bucketIndex * oldRecordsPerBucket,
+ count * sizeof(nsDiskCacheRecord));
+ // clear unused records
+ memset(newRecords + count, 0,
+ (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
+ }
+
+ // Set as the new record array
+ mRecordArray = newArray;
+ mHeader.mRecordCount = newCount;
+
+ InvalidateCache();
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::ShrinkRecords()
+{
+ if (mHeader.mRecordCount <= kMinRecordCount)
+ return NS_OK;
+ CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
+
+ // Verify if we can shrink the record array: all buckets must be less than
+ // 1/2 filled
+ uint32_t maxUsage = 0, bucketIndex;
+ for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
+ if (maxUsage < mHeader.mBucketUsage[bucketIndex])
+ maxUsage = mHeader.mBucketUsage[bucketIndex];
+ }
+ // Determine new bucket size, halve size until maxUsage
+ uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
+ uint32_t newRecordsPerBucket = oldRecordsPerBucket;
+ while (maxUsage < (newRecordsPerBucket >> 1))
+ newRecordsPerBucket >>= 1;
+ if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
+ newRecordsPerBucket = (kMinRecordCount / kBuckets);
+ NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
+ "ShrinkRecords() can't grow records!");
+ if (newRecordsPerBucket == oldRecordsPerBucket)
+ return NS_OK;
+ // Move the buckets close to each other
+ for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
+ // Move bucket
+ memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
+ mRecordArray + bucketIndex * oldRecordsPerBucket,
+ newRecordsPerBucket * sizeof(nsDiskCacheRecord));
+ }
+
+ // Shrink the record array memory block itself
+ uint32_t newCount = newRecordsPerBucket * kBuckets;
+ nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
+ PR_REALLOC(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
+ if (!newArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Set as the new record array
+ mRecordArray = newArray;
+ mHeader.mRecordCount = newCount;
+
+ InvalidateCache();
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::AddRecord( nsDiskCacheRecord * mapRecord,
+ nsDiskCacheRecord * oldRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ const uint32_t count = mHeader.mBucketUsage[bucketIndex];
+
+ oldRecord->SetHashNumber(0); // signify no record
+
+ if (count == GetRecordsPerBucket()) {
+ // Ignore failure to grow the record space, we will then reuse old records
+ GrowRecords();
+ }
+
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ if (count < GetRecordsPerBucket()) {
+ // stick the new record at the end
+ records[count] = *mapRecord;
+ mHeader.mEntryCount++;
+ mHeader.mBucketUsage[bucketIndex]++;
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ InvalidateCache();
+ } else {
+ // Find the record with the highest eviction rank
+ nsDiskCacheRecord * mostEvictable = &records[0];
+ for (int i = count-1; i > 0; i--) {
+ if (records[i].EvictionRank() > mostEvictable->EvictionRank())
+ mostEvictable = &records[i];
+ }
+ *oldRecord = *mostEvictable; // i == GetRecordsPerBucket(), so
+ // evict the mostEvictable
+ *mostEvictable = *mapRecord; // replace it with the new record
+ // check if we need to update mostEvictable entry in header
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ InvalidateCache();
+ }
+
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
+ "eviction rank out of sync");
+ return NS_OK;
+}
+
+
+nsresult
+nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord * mapRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ const uint32_t oldRank = records[i].EvictionRank();
+
+ // stick the new record here
+ records[i] = *mapRecord;
+
+ // update eviction rank in header if necessary
+ if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
+ mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
+ else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+
+ InvalidateCache();
+
+NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
+ "eviction rank out of sync");
+ return NS_OK;
+ }
+ }
+ NS_NOTREACHED("record not found");
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+nsresult
+nsDiskCacheMap::FindRecord( uint32_t hashNumber, nsDiskCacheRecord * result)
+{
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ *result = records[i]; // copy the record
+ NS_ASSERTION(result->ValidRecord(), "bad cache map record");
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord * mapRecord)
+{
+ CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
+
+ const uint32_t hashNumber = mapRecord->HashNumber();
+ const uint32_t bucketIndex = GetBucketIndex(hashNumber);
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+ uint32_t last = mHeader.mBucketUsage[bucketIndex]-1;
+
+ for (int i = last; i >= 0; i--) {
+ if (records[i].HashNumber() == hashNumber) {
+ // found it, now delete it.
+ uint32_t evictionRank = records[i].EvictionRank();
+ NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
+ "evictionRank out of sync");
+ // if not the last record, shift last record into opening
+ records[i] = records[last];
+ records[last].SetHashNumber(0); // clear last record
+ mHeader.mBucketUsage[bucketIndex] = last;
+ mHeader.mEntryCount--;
+
+ // update eviction rank
+ uint32_t bucketIndex = GetBucketIndex(mapRecord->HashNumber());
+ if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ }
+
+ InvalidateCache();
+
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
+ GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+
+int32_t
+nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex,
+ nsDiskCacheRecordVisitor * visitor,
+ uint32_t evictionRank)
+{
+ int32_t rv = kVisitNextRecord;
+ uint32_t count = mHeader.mBucketUsage[bucketIndex];
+ nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
+
+ // call visitor for each entry (matching any eviction rank)
+ for (int i = count-1; i >= 0; i--) {
+ if (evictionRank > records[i].EvictionRank()) continue;
+
+ rv = visitor->VisitRecord(&records[i]);
+ if (rv == kStopVisitingRecords)
+ break; // Stop visiting records
+
+ if (rv == kDeleteRecordAndContinue) {
+ --count;
+ records[i] = records[count];
+ records[count].SetHashNumber(0);
+ InvalidateCache();
+ }
+ }
+
+ if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
+ mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
+ mHeader.mBucketUsage[bucketIndex] = count;
+ // recalc eviction rank
+ mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
+ }
+ NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
+ GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
+
+ return rv;
+}
+
+
+/**
+ * VisitRecords
+ *
+ * Visit every record in cache map in the most convenient order
+ */
+nsresult
+nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor * visitor)
+{
+ for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
+ if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
+ break;
+ }
+ return NS_OK;
+}
+
+
+/**
+ * EvictRecords
+ *
+ * Just like VisitRecords, but visits the records in order of their eviction rank
+ */
+nsresult
+nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
+{
+ uint32_t tempRank[kBuckets];
+ int bucketIndex = 0;
+
+ // copy eviction rank array
+ for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
+ tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
+
+ // Maximum number of iterations determined by number of records
+ // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
+ // the value could decrease if some entry is evicted.
+ int32_t entryCount = mHeader.mEntryCount;
+ for (int n = 0; n < entryCount; ++n) {
+
+ // find bucket with highest eviction rank
+ uint32_t rank = 0;
+ for (int i = 0; i < kBuckets; ++i) {
+ if (rank < tempRank[i]) {
+ rank = tempRank[i];
+ bucketIndex = i;
+ }
+ }
+
+ if (rank == 0) break; // we've examined all the records
+
+ // visit records in bucket with eviction ranks >= target eviction rank
+ if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
+ break;
+
+ // find greatest rank less than 'rank'
+ tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
+ }
+ return NS_OK;
+}
+
+
+
+nsresult
+nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ NS_ENSURE_ARG_POINTER(corruptInfo);
+
+ // create nsIFile for block file
+ nsCOMPtr<nsIFile> blockFile;
+ nsresult rv = NS_OK;
+ *corruptInfo = nsDiskCache::kUnexpectedError;
+
+ for (int i = 0; i < kNumBlockFiles; ++i) {
+ rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
+ if (NS_FAILED(rv)) {
+ *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
+ break;
+ }
+
+ uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
+ uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
+ rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
+ if (NS_FAILED(rv)) {
+ // corruptInfo was set inside the call to mBlockFile[i].Open
+ break;
+ }
+ }
+ // close all files in case of any error
+ if (NS_FAILED(rv))
+ (void)CloseBlockFiles(false); // we already have an error to report
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::CloseBlockFiles(bool flush)
+{
+ nsresult rv, rv2 = NS_OK;
+ for (int i=0; i < kNumBlockFiles; ++i) {
+ rv = mBlockFile[i].Close(flush);
+ if (NS_FAILED(rv)) rv2 = rv; // if one or more errors, report at least one
+ }
+ return rv2;
+}
+
+
+bool
+nsDiskCacheMap::CacheFilesExist()
+{
+ nsCOMPtr<nsIFile> blockFile;
+ nsresult rv;
+
+ for (int i = 0; i < kNumBlockFiles; ++i) {
+ bool exists;
+ rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
+ if (NS_FAILED(rv)) return false;
+
+ rv = blockFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return false;
+ }
+
+ return true;
+}
+
+
+nsresult
+nsDiskCacheMap::CreateCacheSubDirectories()
+{
+ if (!mCacheDirectory)
+ return NS_ERROR_UNEXPECTED;
+
+ for (int32_t index = 0 ; index < 16 ; index++) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = file->AppendNative(nsPrintfCString("%X", index));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+nsDiskCacheEntry *
+nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
+{
+ CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ nsDiskCacheEntry * diskEntry = nullptr;
+ uint32_t metaFile = record->MetaFile();
+ int32_t bytesRead = 0;
+
+ if (!record->MetaLocationInitialized()) return nullptr;
+
+ if (metaFile == 0) { // entry/metadata stored in separate file
+ // open and read the file
+ nsCOMPtr<nsIFile> file;
+ rv = GetLocalFileForDiskCacheRecord(record,
+ nsDiskCache::kMetaData,
+ false,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
+ "[this=%p] reading disk cache entry", this));
+
+ PRFileDesc * fd = nullptr;
+
+ // open the file - restricted to user, the data could be confidential
+ rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ int32_t fileSize = PR_Available(fd);
+ if (fileSize < 0) {
+ // an error occurred. We could call PR_GetError(), but how would that help?
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ rv = EnsureBuffer(fileSize);
+ if (NS_SUCCEEDED(rv)) {
+ bytesRead = PR_Read(fd, mBuffer, fileSize);
+ if (bytesRead < fileSize) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+ PR_Close(fd);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ } else if (metaFile < (kNumBlockFiles + 1)) {
+ // entry/metadata stored in cache block file
+
+ // allocate buffer
+ uint32_t blockCount = record->MetaBlockCount();
+ bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
+
+ rv = EnsureBuffer(bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // read diskEntry, note when the blocks are at the end of file,
+ // bytesRead may be less than blockSize*blockCount.
+ // But the bytesRead should at least agree with the real disk entry size.
+ rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
+ record->MetaStartBlock(),
+ blockCount,
+ &bytesRead);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ diskEntry = (nsDiskCacheEntry *)mBuffer;
+ diskEntry->Unswap(); // disk to memory
+ // Check if calculated size agrees with bytesRead
+ if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
+ return nullptr;
+
+ // Return the buffer containing the diskEntry structure
+ return diskEntry;
+}
+
+
+/**
+ * CreateDiskCacheEntry(nsCacheEntry * entry)
+ *
+ * Prepare an nsCacheEntry for writing to disk
+ */
+nsDiskCacheEntry *
+nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding * binding,
+ uint32_t * aSize)
+{
+ nsCacheEntry * entry = binding->mCacheEntry;
+ if (!entry) return nullptr;
+
+ // Store security info, if it is serializable
+ nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+ if (infoObj && !serializable) return nullptr;
+ if (serializable) {
+ nsCString info;
+ nsresult rv = NS_SerializeToString(serializable, info);
+ if (NS_FAILED(rv)) return nullptr;
+ rv = entry->SetMetaDataElement("security-info", info.get());
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ uint32_t keySize = entry->Key()->Length() + 1;
+ uint32_t metaSize = entry->MetaDataSize();
+ uint32_t size = sizeof(nsDiskCacheEntry) + keySize + metaSize;
+
+ if (aSize) *aSize = size;
+
+ nsresult rv = EnsureBuffer(size);
+ if (NS_FAILED(rv)) return nullptr;
+
+ nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
+ diskEntry->mHeaderVersion = nsDiskCache::kCurrentVersion;
+ diskEntry->mMetaLocation = binding->mRecord.MetaLocation();
+ diskEntry->mFetchCount = entry->FetchCount();
+ diskEntry->mLastFetched = entry->LastFetched();
+ diskEntry->mLastModified = entry->LastModified();
+ diskEntry->mExpirationTime = entry->ExpirationTime();
+ diskEntry->mDataSize = entry->DataSize();
+ diskEntry->mKeySize = keySize;
+ diskEntry->mMetaDataSize = metaSize;
+
+ memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
+
+ rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
+ if (NS_FAILED(rv)) return nullptr;
+
+ return diskEntry;
+}
+
+
+nsresult
+nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding * binding)
+{
+ CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
+ binding->mRecord.HashNumber()));
+
+ nsresult rv = NS_OK;
+ uint32_t size;
+ nsDiskCacheEntry * diskEntry = CreateDiskCacheEntry(binding, &size);
+ if (!diskEntry) return NS_ERROR_UNEXPECTED;
+
+ uint32_t fileIndex = CalculateFileIndex(size);
+
+ // Deallocate old storage if necessary
+ if (binding->mRecord.MetaLocationInitialized()) {
+ // we have existing storage
+
+ if ((binding->mRecord.MetaFile() == 0) &&
+ (fileIndex == 0)) { // keeping the separate file
+ // just decrement total
+ DecrementTotalSize(binding->mRecord.MetaFileSize());
+ NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
+ "generations out of sync");
+ } else {
+ rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
+ // write entry data to disk cache block file
+ diskEntry->Swap();
+
+ if (fileIndex != 0) {
+ while (1) {
+ uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
+ uint32_t blocks = ((size - 1) / blockSize) + 1;
+
+ int32_t startBlock;
+ rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
+ &startBlock);
+ if (NS_SUCCEEDED(rv)) {
+ // update binding and cache map record
+ binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
+
+ rv = UpdateRecord(&binding->mRecord);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX we should probably write out bucket ourselves
+
+ IncrementTotalSize(blocks, blockSize);
+ break;
+ }
+
+ if (fileIndex == kNumBlockFiles) {
+ fileIndex = 0; // write data to separate file
+ break;
+ }
+
+ // try next block file
+ fileIndex++;
+ }
+ }
+
+ if (fileIndex == 0) {
+ // Write entry data to separate file
+ uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
+ if (metaFileSizeK > kMaxDataSizeK)
+ metaFileSizeK = kMaxDataSizeK;
+
+ binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
+ binding->mRecord.SetMetaFileSize(metaFileSizeK);
+ rv = UpdateRecord(&binding->mRecord);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
+ nsDiskCache::kMetaData,
+ true,
+ getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // open the file
+ PRFileDesc * fd;
+ // open the file - restricted to user, the data could be confidential
+ rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the file
+ int32_t bytesWritten = PR_Write(fd, diskEntry, size);
+
+ PRStatus err = PR_Close(fd);
+ if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ IncrementTotalSize(metaFileSizeK);
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
+{
+ CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
+ binding->mRecord.HashNumber(), size));
+
+ uint32_t fileIndex = binding->mRecord.DataFile();
+ int32_t readSize = size;
+
+ nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
+ binding->mRecord.DataStartBlock(),
+ binding->mRecord.DataBlockCount(),
+ &readSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (readSize < (int32_t)size) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
+{
+ CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
+ binding->mRecord.HashNumber(), size));
+
+ nsresult rv = NS_OK;
+
+ // determine block file & number of blocks
+ uint32_t fileIndex = CalculateFileIndex(size);
+ uint32_t blockCount = 0;
+ int32_t startBlock = 0;
+
+ if (size > 0) {
+ // if fileIndex is 0, bad things happen below, which makes gcc 4.7
+ // complain, but it's not supposed to happen. See bug 854105.
+ MOZ_ASSERT(fileIndex);
+ while (fileIndex) {
+ uint32_t blockSize = GetBlockSizeForIndex(fileIndex);
+ blockCount = ((size - 1) / blockSize) + 1;
+
+ rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
+ &startBlock);
+ if (NS_SUCCEEDED(rv)) {
+ IncrementTotalSize(blockCount, blockSize);
+ break;
+ }
+
+ if (fileIndex == kNumBlockFiles)
+ return rv;
+
+ fileIndex++;
+ }
+ }
+
+ // update binding and cache map record
+ binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
+ if (!binding->mDoomed) {
+ rv = UpdateRecord(&binding->mRecord);
+ }
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
+{
+ nsresult rv1 = DeleteStorage(record, nsDiskCache::kData);
+ nsresult rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
+ return NS_FAILED(rv1) ? rv1 : rv2;
+}
+
+
+nsresult
+nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
+{
+ CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
+ metaData));
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t fileIndex = metaData ? record->MetaFile() : record->DataFile();
+ nsCOMPtr<nsIFile> file;
+
+ if (fileIndex == 0) {
+ // delete the file
+ uint32_t sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
+ // XXX if sizeK == USHRT_MAX, stat file for actual size
+
+ rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->Remove(false); // false == non-recursive
+ }
+ DecrementTotalSize(sizeK);
+
+ } else if (fileIndex < (kNumBlockFiles + 1)) {
+ // deallocate blocks
+ uint32_t startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
+ uint32_t blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
+
+ rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
+ DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
+ }
+ if (metaData) record->ClearMetaLocation();
+ else record->ClearDataLocation();
+
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result)
+{
+ if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t hash = record->HashNumber();
+
+ // The file is stored under subdirectories according to the hash number:
+ // 0x01234567 -> 0/12/
+ rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
+ if (NS_FAILED(rv)) return rv;
+ rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ int16_t generation = record->Generation();
+ char name[32];
+ // Cut the beginning of the hash that was used in the path
+ ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
+ generation);
+ rv = file->AppendNative(nsDependentCString(name));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFileForDiskCacheRecord(record,
+ meta,
+ createPath,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+ return rv;
+}
+
+
+nsresult
+nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
+{
+ if (!mCacheDirectory) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ char name[32];
+ ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
+ rv = file->AppendNative(nsDependentCString(name));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_IF_ADDREF(*result = file);
+
+ return rv;
+}
+
+
+uint32_t
+nsDiskCacheMap::CalculateFileIndex(uint32_t size)
+{
+ // We prefer to use block file with larger block if the wasted space would
+ // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
+ // instead of in 4 1K-blocks.
+
+ if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
+ if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
+ if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
+ return 0;
+}
+
+nsresult
+nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
+{
+ if (mBufferSize < bufSize) {
+ char * buf = (char *)PR_REALLOC(mBuffer, bufSize);
+ if (!buf) {
+ mBufferSize = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBuffer = buf;
+ mBufferSize = bufSize;
+ }
+ return NS_OK;
+}
+
+void
+nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
+{
+ // Heuristic 1. average cache entry size is probably around 1KB
+ // Heuristic 2. we don't want more than 32MB reserved to store the record
+ // map in memory.
+ const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
+ int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
+ if (mMaxRecordCount < maxRecordCount) {
+ // We can only grow
+ mMaxRecordCount = maxRecordCount;
+ }
+}
+
+size_t
+nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(mRecordArray);
+
+ usage += aMallocSizeOf(mBuffer);
+ usage += aMallocSizeOf(mMapFD);
+ usage += aMallocSizeOf(mCleanFD);
+ usage += aMallocSizeOf(mCacheDirectory);
+ usage += aMallocSizeOf(mCleanCacheTimer);
+
+ for (int i = 0; i < kNumBlockFiles; i++) {
+ usage += mBlockFile[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return usage;
+}
+
+nsresult
+nsDiskCacheMap::InitCacheClean(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo)
+{
+ // The _CACHE_CLEAN_ file will be used in the future to determine
+ // if the cache is clean or not.
+ bool cacheCleanFileExists = false;
+ nsCOMPtr<nsIFile> cacheCleanFile;
+ nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheCleanFile->AppendNative(
+ NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
+ if (NS_SUCCEEDED(rv)) {
+ // Check if the file already exists, if it does, we will later read the
+ // value and report it to telemetry.
+ cacheCleanFile->Exists(&cacheCleanFileExists);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not build cache clean file path");
+ *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
+ return rv;
+ }
+
+ // Make sure the _CACHE_CLEAN_ file exists
+ rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
+ 00600, &mCleanFD);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not open cache clean file");
+ *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
+ return rv;
+ }
+
+ if (cacheCleanFileExists) {
+ char clean = '0';
+ int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
+ if (bytesRead != 1) {
+ NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
+ }
+ }
+
+ // Create a timer that will be used to validate the cache
+ // as long as an activity threshold was met
+ mCleanCacheTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mCleanCacheTimer->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread);
+ rv = ResetCacheTimer();
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not create cache clean timer");
+ mCleanCacheTimer = nullptr;
+ *corruptInfo = nsDiskCache::kCacheCleanTimerError;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::WriteCacheClean(bool clean)
+{
+ nsCacheService::AssertOwnsLock();
+ if (!mCleanFD) {
+ NS_WARNING("Cache clean file is not open!");
+ return NS_ERROR_FAILURE;
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
+ // I'm using a simple '1' or '0' to denote cache clean
+ // since it can be edited easily by any text editor for testing.
+ char data = clean? '1' : '0';
+ int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
+ if (filePos != 0) {
+ NS_WARNING("Could not seek in cache clean file!");
+ return NS_ERROR_FAILURE;
+ }
+ int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
+ if (bytesWritten != 1) {
+ NS_WARNING("Could not write cache clean file!");
+ return NS_ERROR_FAILURE;
+ }
+ PRStatus err = PR_Sync(mCleanFD);
+ if (err != PR_SUCCESS) {
+ NS_WARNING("Could not flush cache clean file!");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::InvalidateCache()
+{
+ nsCacheService::AssertOwnsLock();
+ CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
+ nsresult rv;
+
+ if (!mIsDirtyCacheFlushed) {
+ rv = WriteCacheClean(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mIsDirtyCacheFlushed = true;
+ }
+
+ rv = ResetCacheTimer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
+{
+ mCleanCacheTimer->Cancel();
+ nsresult rv =
+ mCleanCacheTimer->InitWithFuncCallback(RevalidateTimerCallback,
+ nullptr, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mLastInvalidateTime = PR_IntervalNow();
+
+ return rv;
+}
+
+void
+nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
+{
+ nsCacheServiceAutoLock lock;
+ if (!nsCacheService::gService->mDiskDevice ||
+ !nsCacheService::gService->mDiskDevice->Initialized()) {
+ return;
+ }
+
+ nsDiskCacheMap *diskCacheMap =
+ &nsCacheService::gService->mDiskDevice->mCacheMap;
+
+ // If we have less than kRevalidateCacheTimeout since the last timer was
+ // issued then another thread called InvalidateCache. This won't catch
+ // all cases where we wanted to cancel the timer, but under the lock it
+ // is always OK to revalidate as long as IsCacheInSafeState() returns
+ // true. We just want to avoid revalidating when we can to reduce IO
+ // and this check will do that.
+ uint32_t delta =
+ PR_IntervalToMilliseconds(PR_IntervalNow() -
+ diskCacheMap->mLastInvalidateTime) +
+ kRevalidateCacheTimeoutTolerance;
+ if (delta < kRevalidateCacheTimeout) {
+ diskCacheMap->ResetCacheTimer();
+ return;
+ }
+
+ nsresult rv = diskCacheMap->RevalidateCache();
+ if (NS_FAILED(rv)) {
+ diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
+ }
+}
+
+bool
+nsDiskCacheMap::IsCacheInSafeState()
+{
+ return nsCacheService::GlobalInstance()->IsDoomListEmpty();
+}
+
+nsresult
+nsDiskCacheMap::RevalidateCache()
+{
+ CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
+ nsresult rv;
+
+ if (!IsCacheInSafeState()) {
+ CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
+ "cache not in a safe state\n"));
+ // Normally we would return an error here, but there is a bug where
+ // the doom list sometimes gets an entry 'stuck' and doens't clear it
+ // until browser shutdown. So we allow revalidation for the time being
+ // to get proper telemetry data of how much the cache corruption plan
+ // would help.
+ }
+
+ // If telemetry data shows it is worth it, we'll be flushing headers and
+ // records before flushing the clean cache file.
+
+ // Write out the _CACHE_CLEAN_ file with '1'
+ rv = WriteCacheClean(true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mIsDirtyCacheFlushed = false;
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheMap.h b/netwerk/cache/nsDiskCacheMap.h
new file mode 100644
index 000000000..77af2586f
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheMap.h
@@ -0,0 +1,577 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsDiskCacheMap_h_
+#define _nsDiskCacheMap_h_
+
+#include "mozilla/MemoryReporting.h"
+#include <limits.h>
+
+#include "prnetdb.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+
+#include "nsDiskCache.h"
+#include "nsDiskCacheBlockFile.h"
+
+
+class nsDiskCacheBinding;
+struct nsDiskCacheEntry;
+
+/******************************************************************************
+ * nsDiskCacheRecord
+ *
+ * Cache Location Format
+ *
+ * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit
+ *
+ * 0011 0000 0000 0000 0000 0000 0000 0000 : File Selector (0 = separate file)
+ * 0000 0011 0000 0000 0000 0000 0000 0000 : number of extra contiguous blocks 1-4
+ * 0100 1100 0000 0000 0000 0000 0000 0000 : reserved bits
+ * 0000 0000 1111 1111 1111 1111 1111 1111 : block# 0-16777216 (2^24)
+ *
+ * 0000 0000 1111 1111 1111 1111 0000 0000 : eFileSizeMask (size of file in k: see note)
+ * 0000 0000 0000 0000 0000 0000 1111 1111 : eFileGenerationMask
+ *
+ * File Selector:
+ * 0 = separate file on disk
+ * 1 = 256 byte block file
+ * 2 = 1k block file
+ * 3 = 4k block file
+ *
+ * eFileSizeMask note: Files larger than 65535 KiB have this limit stored in
+ * the location. The file itself must be examined to
+ * determine its actual size if necessary.
+ *
+ *****************************************************************************/
+
+/*
+ We have 3 block files with roughly the same max size (32MB)
+ 1 - block size 256B, number of blocks 131072
+ 2 - block size 1kB, number of blocks 32768
+ 3 - block size 4kB, number of blocks 8192
+*/
+#define kNumBlockFiles 3
+#define SIZE_SHIFT(idx) (2 * ((idx) - 1))
+#define BLOCK_SIZE_FOR_INDEX(idx) ((idx) ? (256 << SIZE_SHIFT(idx)) : 0)
+#define BITMAP_SIZE_FOR_INDEX(idx) ((idx) ? (131072 >> SIZE_SHIFT(idx)) : 0)
+
+// Min and max values for the number of records in the DiskCachemap
+#define kMinRecordCount 512
+
+#define kSeparateFile 0
+#define kBuckets (1 << 5) // must be a power of 2!
+
+// Maximum size in K which can be stored in the location (see eFileSizeMask).
+// Both data and metadata can be larger, but only up to kMaxDataSizeK can be
+// counted into total cache size. I.e. if there are entries where either data or
+// metadata is larger than kMaxDataSizeK, the total cache size will be
+// inaccurate (smaller) than the actual cache size. The alternative is to stat
+// the files to find the real size, which was decided against for performance
+// reasons. See bug #651100 comment #21.
+#define kMaxDataSizeK 0xFFFF
+
+// preallocate up to 1MB of separate cache file
+#define kPreallocateLimit 1 * 1024 * 1024
+
+// The minimum amount of milliseconds to wait before re-attempting to
+// revalidate the cache.
+#define kRevalidateCacheTimeout 3000
+#define kRevalidateCacheTimeoutTolerance 10
+#define kRevalidateCacheErrorTimeout 1000
+
+class nsDiskCacheRecord {
+
+private:
+ uint32_t mHashNumber;
+ uint32_t mEvictionRank;
+ uint32_t mDataLocation;
+ uint32_t mMetaLocation;
+
+ enum {
+ eLocationInitializedMask = 0x80000000,
+
+ eLocationSelectorMask = 0x30000000,
+ eLocationSelectorOffset = 28,
+
+ eExtraBlocksMask = 0x03000000,
+ eExtraBlocksOffset = 24,
+
+ eReservedMask = 0x4C000000,
+
+ eBlockNumberMask = 0x00FFFFFF,
+
+ eFileSizeMask = 0x00FFFF00,
+ eFileSizeOffset = 8,
+ eFileGenerationMask = 0x000000FF,
+ eFileReservedMask = 0x4F000000
+
+ };
+
+public:
+ nsDiskCacheRecord()
+ : mHashNumber(0), mEvictionRank(0), mDataLocation(0), mMetaLocation(0)
+ {
+ }
+
+ bool ValidRecord()
+ {
+ if ((mDataLocation & eReservedMask) || (mMetaLocation & eReservedMask))
+ return false;
+ return true;
+ }
+
+ // HashNumber accessors
+ uint32_t HashNumber() const { return mHashNumber; }
+ void SetHashNumber( uint32_t hashNumber) { mHashNumber = hashNumber; }
+
+ // EvictionRank accessors
+ uint32_t EvictionRank() const { return mEvictionRank; }
+ void SetEvictionRank( uint32_t rank) { mEvictionRank = rank ? rank : 1; }
+
+ // DataLocation accessors
+ bool DataLocationInitialized() const { return 0 != (mDataLocation & eLocationInitializedMask); }
+ void ClearDataLocation() { mDataLocation = 0; }
+
+ uint32_t DataFile() const
+ {
+ return (uint32_t)(mDataLocation & eLocationSelectorMask) >> eLocationSelectorOffset;
+ }
+
+ void SetDataBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount)
+ {
+ // clear everything
+ mDataLocation = 0;
+
+ // set file index
+ NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
+ NS_ASSERTION( index > 0,"invalid location index");
+ mDataLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
+
+ // set startBlock
+ NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
+ mDataLocation |= startBlock & eBlockNumberMask;
+
+ // set blockCount
+ NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count");
+ --blockCount;
+ mDataLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask;
+
+ mDataLocation |= eLocationInitializedMask;
+ }
+
+ uint32_t DataBlockCount() const
+ {
+ return (uint32_t)((mDataLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1;
+ }
+
+ uint32_t DataStartBlock() const
+ {
+ return (mDataLocation & eBlockNumberMask);
+ }
+
+ uint32_t DataBlockSize() const
+ {
+ return BLOCK_SIZE_FOR_INDEX(DataFile());
+ }
+
+ uint32_t DataFileSize() const { return (mDataLocation & eFileSizeMask) >> eFileSizeOffset; }
+ void SetDataFileSize(uint32_t size)
+ {
+ NS_ASSERTION((mDataLocation & eFileReservedMask) == 0, "bad location");
+ mDataLocation &= ~eFileSizeMask; // clear eFileSizeMask
+ mDataLocation |= (size << eFileSizeOffset) & eFileSizeMask;
+ }
+
+ uint8_t DataFileGeneration() const
+ {
+ return (mDataLocation & eFileGenerationMask);
+ }
+
+ void SetDataFileGeneration( uint8_t generation)
+ {
+ // clear everything, (separate file index = 0)
+ mDataLocation = 0;
+ mDataLocation |= generation & eFileGenerationMask;
+ mDataLocation |= eLocationInitializedMask;
+ }
+
+ // MetaLocation accessors
+ bool MetaLocationInitialized() const { return 0 != (mMetaLocation & eLocationInitializedMask); }
+ void ClearMetaLocation() { mMetaLocation = 0; }
+ uint32_t MetaLocation() const { return mMetaLocation; }
+
+ uint32_t MetaFile() const
+ {
+ return (uint32_t)(mMetaLocation & eLocationSelectorMask) >> eLocationSelectorOffset;
+ }
+
+ void SetMetaBlocks( uint32_t index, uint32_t startBlock, uint32_t blockCount)
+ {
+ // clear everything
+ mMetaLocation = 0;
+
+ // set file index
+ NS_ASSERTION( index < (kNumBlockFiles + 1), "invalid location index");
+ NS_ASSERTION( index > 0, "invalid location index");
+ mMetaLocation |= (index << eLocationSelectorOffset) & eLocationSelectorMask;
+
+ // set startBlock
+ NS_ASSERTION(startBlock == (startBlock & eBlockNumberMask), "invalid block number");
+ mMetaLocation |= startBlock & eBlockNumberMask;
+
+ // set blockCount
+ NS_ASSERTION( (blockCount>=1) && (blockCount<=4),"invalid block count");
+ --blockCount;
+ mMetaLocation |= (blockCount << eExtraBlocksOffset) & eExtraBlocksMask;
+
+ mMetaLocation |= eLocationInitializedMask;
+ }
+
+ uint32_t MetaBlockCount() const
+ {
+ return (uint32_t)((mMetaLocation & eExtraBlocksMask) >> eExtraBlocksOffset) + 1;
+ }
+
+ uint32_t MetaStartBlock() const
+ {
+ return (mMetaLocation & eBlockNumberMask);
+ }
+
+ uint32_t MetaBlockSize() const
+ {
+ return BLOCK_SIZE_FOR_INDEX(MetaFile());
+ }
+
+ uint32_t MetaFileSize() const { return (mMetaLocation & eFileSizeMask) >> eFileSizeOffset; }
+ void SetMetaFileSize(uint32_t size)
+ {
+ mMetaLocation &= ~eFileSizeMask; // clear eFileSizeMask
+ mMetaLocation |= (size << eFileSizeOffset) & eFileSizeMask;
+ }
+
+ uint8_t MetaFileGeneration() const
+ {
+ return (mMetaLocation & eFileGenerationMask);
+ }
+
+ void SetMetaFileGeneration( uint8_t generation)
+ {
+ // clear everything, (separate file index = 0)
+ mMetaLocation = 0;
+ mMetaLocation |= generation & eFileGenerationMask;
+ mMetaLocation |= eLocationInitializedMask;
+ }
+
+ uint8_t Generation() const
+ {
+ if ((mDataLocation & eLocationInitializedMask) &&
+ (DataFile() == 0))
+ return DataFileGeneration();
+
+ if ((mMetaLocation & eLocationInitializedMask) &&
+ (MetaFile() == 0))
+ return MetaFileGeneration();
+
+ return 0; // no generation
+ }
+
+#if defined(IS_LITTLE_ENDIAN)
+ void Swap()
+ {
+ mHashNumber = htonl(mHashNumber);
+ mEvictionRank = htonl(mEvictionRank);
+ mDataLocation = htonl(mDataLocation);
+ mMetaLocation = htonl(mMetaLocation);
+ }
+#endif
+
+#if defined(IS_LITTLE_ENDIAN)
+ void Unswap()
+ {
+ mHashNumber = ntohl(mHashNumber);
+ mEvictionRank = ntohl(mEvictionRank);
+ mDataLocation = ntohl(mDataLocation);
+ mMetaLocation = ntohl(mMetaLocation);
+ }
+#endif
+
+};
+
+
+/******************************************************************************
+ * nsDiskCacheRecordVisitor
+ *****************************************************************************/
+
+enum { kDeleteRecordAndContinue = -1,
+ kStopVisitingRecords = 0,
+ kVisitNextRecord = 1
+};
+
+class nsDiskCacheRecordVisitor {
+ public:
+
+ virtual int32_t VisitRecord( nsDiskCacheRecord * mapRecord) = 0;
+};
+
+
+/******************************************************************************
+ * nsDiskCacheHeader
+ *****************************************************************************/
+
+struct nsDiskCacheHeader {
+ uint32_t mVersion; // cache version.
+ uint32_t mDataSize; // size of cache in units of 1024bytes.
+ int32_t mEntryCount; // number of entries stored in cache.
+ uint32_t mIsDirty; // dirty flag.
+ int32_t mRecordCount; // Number of records
+ uint32_t mEvictionRank[kBuckets]; // Highest EvictionRank of the bucket
+ uint32_t mBucketUsage[kBuckets]; // Number of used entries in the bucket
+
+ nsDiskCacheHeader()
+ : mVersion(nsDiskCache::kCurrentVersion)
+ , mDataSize(0)
+ , mEntryCount(0)
+ , mIsDirty(true)
+ , mRecordCount(0)
+ {}
+
+ void Swap()
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mVersion = htonl(mVersion);
+ mDataSize = htonl(mDataSize);
+ mEntryCount = htonl(mEntryCount);
+ mIsDirty = htonl(mIsDirty);
+ mRecordCount = htonl(mRecordCount);
+
+ for (uint32_t i = 0; i < kBuckets ; i++) {
+ mEvictionRank[i] = htonl(mEvictionRank[i]);
+ mBucketUsage[i] = htonl(mBucketUsage[i]);
+ }
+#endif
+ }
+
+ void Unswap()
+ {
+#if defined(IS_LITTLE_ENDIAN)
+ mVersion = ntohl(mVersion);
+ mDataSize = ntohl(mDataSize);
+ mEntryCount = ntohl(mEntryCount);
+ mIsDirty = ntohl(mIsDirty);
+ mRecordCount = ntohl(mRecordCount);
+
+ for (uint32_t i = 0; i < kBuckets ; i++) {
+ mEvictionRank[i] = ntohl(mEvictionRank[i]);
+ mBucketUsage[i] = ntohl(mBucketUsage[i]);
+ }
+#endif
+ }
+};
+
+
+/******************************************************************************
+ * nsDiskCacheMap
+ *****************************************************************************/
+
+class nsDiskCacheMap {
+public:
+
+ nsDiskCacheMap() :
+ mCacheDirectory(nullptr),
+ mMapFD(nullptr),
+ mCleanFD(nullptr),
+ mRecordArray(nullptr),
+ mBufferSize(0),
+ mBuffer(nullptr),
+ mMaxRecordCount(16384), // this default value won't matter
+ mIsDirtyCacheFlushed(false),
+ mLastInvalidateTime(0)
+ { }
+
+ ~nsDiskCacheMap()
+ {
+ (void) Close(true);
+ }
+
+/**
+ * File Operations
+ *
+ * Open
+ *
+ * Creates a new cache map file if one doesn't exist.
+ * Returns error if it detects change in format or cache wasn't closed.
+ */
+ nsresult Open( nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult Close(bool flush);
+ nsresult Trim();
+
+ nsresult FlushHeader();
+ nsresult FlushRecords( bool unswap);
+
+ void NotifyCapacityChange(uint32_t capacity);
+
+/**
+ * Record operations
+ */
+ nsresult AddRecord( nsDiskCacheRecord * mapRecord, nsDiskCacheRecord * oldRecord);
+ nsresult UpdateRecord( nsDiskCacheRecord * mapRecord);
+ nsresult FindRecord( uint32_t hashNumber, nsDiskCacheRecord * mapRecord);
+ nsresult DeleteRecord( nsDiskCacheRecord * mapRecord);
+ nsresult VisitRecords( nsDiskCacheRecordVisitor * visitor);
+ nsresult EvictRecords( nsDiskCacheRecordVisitor * visitor);
+
+/**
+ * Disk Entry operations
+ */
+ nsresult DeleteStorage( nsDiskCacheRecord * record);
+
+ nsresult GetFileForDiskCacheRecord( nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result);
+
+ nsresult GetLocalFileForDiskCacheRecord( nsDiskCacheRecord * record,
+ bool meta,
+ bool createPath,
+ nsIFile ** result);
+
+ // On success, this returns the buffer owned by nsDiskCacheMap,
+ // so it must not be deleted by the caller.
+ nsDiskCacheEntry * ReadDiskCacheEntry( nsDiskCacheRecord * record);
+
+ nsresult WriteDiskCacheEntry( nsDiskCacheBinding * binding);
+
+ nsresult ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size);
+ nsresult WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size);
+ nsresult DeleteStorage( nsDiskCacheRecord * record, bool metaData);
+
+ /**
+ * Statistical Operations
+ */
+ void IncrementTotalSize( uint32_t delta)
+ {
+ mHeader.mDataSize += delta;
+ mHeader.mIsDirty = true;
+ }
+
+ void DecrementTotalSize( uint32_t delta)
+ {
+ NS_ASSERTION(mHeader.mDataSize >= delta, "disk cache size negative?");
+ mHeader.mDataSize = mHeader.mDataSize > delta ? mHeader.mDataSize - delta : 0;
+ mHeader.mIsDirty = true;
+ }
+
+ inline void IncrementTotalSize( uint32_t blocks, uint32_t blockSize)
+ {
+ // Round up to nearest K
+ IncrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10);
+ }
+
+ inline void DecrementTotalSize( uint32_t blocks, uint32_t blockSize)
+ {
+ // Round up to nearest K
+ DecrementTotalSize(((blocks*blockSize) + 0x03FF) >> 10);
+ }
+
+ uint32_t TotalSize() { return mHeader.mDataSize; }
+
+ int32_t EntryCount() { return mHeader.mEntryCount; }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+
+private:
+
+ /**
+ * Private methods
+ */
+ nsresult OpenBlockFiles(nsDiskCache::CorruptCacheInfo * corruptInfo);
+ nsresult CloseBlockFiles(bool flush);
+ bool CacheFilesExist();
+
+ nsresult CreateCacheSubDirectories();
+
+ uint32_t CalculateFileIndex(uint32_t size);
+
+ nsresult GetBlockFileForIndex( uint32_t index, nsIFile ** result);
+ uint32_t GetBlockSizeForIndex( uint32_t index) const {
+ return BLOCK_SIZE_FOR_INDEX(index);
+ }
+ uint32_t GetBitMapSizeForIndex( uint32_t index) const {
+ return BITMAP_SIZE_FOR_INDEX(index);
+ }
+
+ // returns the bucket number
+ uint32_t GetBucketIndex( uint32_t hashNumber) const {
+ return (hashNumber & (kBuckets - 1));
+ }
+
+ // Gets the size of the bucket (in number of records)
+ uint32_t GetRecordsPerBucket() const {
+ return mHeader.mRecordCount / kBuckets;
+ }
+
+ // Gets the first record in the bucket
+ nsDiskCacheRecord *GetFirstRecordInBucket(uint32_t bucket) const {
+ return mRecordArray + bucket * GetRecordsPerBucket();
+ }
+
+ uint32_t GetBucketRank(uint32_t bucketIndex, uint32_t targetRank);
+
+ int32_t VisitEachRecord(uint32_t bucketIndex,
+ nsDiskCacheRecordVisitor * visitor,
+ uint32_t evictionRank);
+
+ nsresult GrowRecords();
+ nsresult ShrinkRecords();
+
+ nsresult EnsureBuffer(uint32_t bufSize);
+
+ // The returned structure will point to the buffer owned by nsDiskCacheMap,
+ // so it must not be deleted by the caller.
+ nsDiskCacheEntry * CreateDiskCacheEntry(nsDiskCacheBinding * binding,
+ uint32_t * size);
+
+ // Initializes the _CACHE_CLEAN_ related functionality
+ nsresult InitCacheClean(nsIFile * cacheDirectory,
+ nsDiskCache::CorruptCacheInfo * corruptInfo);
+ // Writes out a value of '0' or '1' in the _CACHE_CLEAN_ file
+ nsresult WriteCacheClean(bool clean);
+ // Resets the timout for revalidating the cache
+ nsresult ResetCacheTimer(int32_t timeout = kRevalidateCacheTimeout);
+ // Invalidates the cache, calls WriteCacheClean and ResetCacheTimer
+ nsresult InvalidateCache();
+ // Determines if the cache is in a safe state
+ bool IsCacheInSafeState();
+ // Revalidates the cache by writting out the header, records, and finally
+ // by calling WriteCacheClean(true).
+ nsresult RevalidateCache();
+ // Timer which revalidates the cache
+ static void RevalidateTimerCallback(nsITimer *aTimer, void *arg);
+
+/**
+ * data members
+ */
+private:
+ nsCOMPtr<nsITimer> mCleanCacheTimer;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ PRFileDesc * mMapFD;
+ PRFileDesc * mCleanFD;
+ nsDiskCacheRecord * mRecordArray;
+ nsDiskCacheBlockFile mBlockFile[kNumBlockFiles];
+ uint32_t mBufferSize;
+ char * mBuffer;
+ nsDiskCacheHeader mHeader;
+ int32_t mMaxRecordCount;
+ bool mIsDirtyCacheFlushed;
+ PRIntervalTime mLastInvalidateTime;
+};
+
+#endif // _nsDiskCacheMap_h_
diff --git a/netwerk/cache/nsDiskCacheStreams.cpp b/netwerk/cache/nsDiskCacheStreams.cpp
new file mode 100644
index 000000000..d58cca7fd
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheStreams.cpp
@@ -0,0 +1,693 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheStreams.h"
+#include "nsCacheService.h"
+#include "mozilla/FileUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include <algorithm>
+
+// we pick 16k as the max buffer size because that is the threshold above which
+// we are unable to store the data in the cache block files
+// see nsDiskCacheMap.[cpp,h]
+#define kMaxBufferSize (16 * 1024)
+
+// Assumptions:
+// - cache descriptors live for life of streams
+// - streams will only be used by FileTransport,
+// they will not be directly accessible to clients
+// - overlapped I/O is NOT supported
+
+
+/******************************************************************************
+ * nsDiskCacheInputStream
+ *****************************************************************************/
+class nsDiskCacheInputStream : public nsIInputStream {
+
+public:
+
+ nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
+ PRFileDesc * fileDesc,
+ const char * buffer,
+ uint32_t endOfStream);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+private:
+ virtual ~nsDiskCacheInputStream();
+
+ nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
+ PRFileDesc * mFD;
+ const char * mBuffer;
+ uint32_t mStreamEnd;
+ uint32_t mPos; // stream position
+ bool mClosed;
+};
+
+
+NS_IMPL_ISUPPORTS(nsDiskCacheInputStream, nsIInputStream)
+
+
+nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
+ PRFileDesc * fileDesc,
+ const char * buffer,
+ uint32_t endOfStream)
+ : mStreamIO(parent)
+ , mFD(fileDesc)
+ , mBuffer(buffer)
+ , mStreamEnd(endOfStream)
+ , mPos(0)
+ , mClosed(false)
+{
+ NS_ADDREF(mStreamIO);
+ mStreamIO->IncrementInputStreamCount();
+}
+
+
+nsDiskCacheInputStream::~nsDiskCacheInputStream()
+{
+ Close();
+ mStreamIO->DecrementInputStreamCount();
+ NS_RELEASE(mStreamIO);
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Close()
+{
+ if (!mClosed) {
+ if (mFD) {
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mClosed = true;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Available(uint64_t * bytesAvailable)
+{
+ if (mClosed) return NS_BASE_STREAM_CLOSED;
+ if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED;
+
+ *bytesAvailable = mStreamEnd - mPos;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::Read(char * buffer, uint32_t count, uint32_t * bytesRead)
+{
+ *bytesRead = 0;
+
+ if (mClosed) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream was closed",
+ this, buffer, count));
+ return NS_OK;
+ }
+
+ if (mPos == mStreamEnd) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream at end of file",
+ this, buffer, count));
+ return NS_OK;
+ }
+ if (mPos > mStreamEnd) {
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p] stream past end of file (!)",
+ this, buffer, count));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (count > mStreamEnd - mPos)
+ count = mStreamEnd - mPos;
+
+ if (mFD) {
+ // just read from file
+ int32_t result = PR_Read(mFD, buffer, count);
+ if (result < 0) {
+ nsresult rv = NS_ErrorAccordingToNSPR();
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read PR_Read failed"
+ "[stream=%p, rv=%d, NSPR error %s",
+ this, int(rv), PR_ErrorToName(PR_GetError())));
+ return rv;
+ }
+
+ mPos += (uint32_t)result;
+ *bytesRead = (uint32_t)result;
+
+ } else if (mBuffer) {
+ // read data from mBuffer
+ memcpy(buffer, mBuffer + mPos, count);
+ mPos += count;
+ *bytesRead = count;
+ } else {
+ // no data source for input stream
+ }
+
+ CACHE_LOG_DEBUG(("CACHE: nsDiskCacheInputStream::Read "
+ "[stream=%p, count=%ud, byteRead=%ud] ",
+ this, unsigned(count), unsigned(*bytesRead)));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
+ void * closure,
+ uint32_t count,
+ uint32_t * bytesRead)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+
+
+
+/******************************************************************************
+ * nsDiskCacheStreamIO
+ *****************************************************************************/
+NS_IMPL_ISUPPORTS(nsDiskCacheStreamIO, nsIOutputStream)
+
+nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
+ : mBinding(binding)
+ , mInStreamCount(0)
+ , mFD(nullptr)
+ , mStreamEnd(0)
+ , mBufSize(0)
+ , mBuffer(nullptr)
+ , mOutputStreamIsOpen(false)
+{
+ mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
+
+ // acquire "death grip" on cache service
+ nsCacheService *service = nsCacheService::GlobalInstance();
+ NS_ADDREF(service);
+}
+
+
+nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
+{
+ nsCacheService::AssertOwnsLock();
+
+ // Close the outputstream
+ if (mBinding && mOutputStreamIsOpen) {
+ (void)CloseOutputStream();
+ }
+
+ // release "death grip" on cache service
+ nsCacheService *service = nsCacheService::GlobalInstance();
+ NS_RELEASE(service);
+
+ // assert streams closed
+ NS_ASSERTION(!mOutputStreamIsOpen, "output stream still open");
+ NS_ASSERTION(mInStreamCount == 0, "input stream still open");
+ NS_ASSERTION(!mFD, "file descriptor not closed");
+
+ DeleteBuffer();
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::GetInputStream(uint32_t offset, nsIInputStream ** inputStream)
+{
+ NS_ENSURE_ARG_POINTER(inputStream);
+ NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
+
+ *inputStream = nullptr;
+
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mOutputStreamIsOpen) {
+ NS_WARNING("already have an output stream open");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ PRFileDesc * fd = nullptr;
+
+ mStreamEnd = mBinding->mCacheEntry->DataSize();
+ if (mStreamEnd == 0) {
+ // there's no data to read
+ NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
+ } else if (mBinding->mRecord.DataFile() == 0) {
+ // open file desc for data
+ rv = OpenCacheFile(PR_RDONLY, &fd);
+ if (NS_FAILED(rv)) return rv; // unable to open file
+ NS_ASSERTION(fd, "cache stream lacking open file.");
+
+ } else if (!mBuffer) {
+ // read block file for data
+ rv = ReadCacheBlocks(mStreamEnd);
+ if (NS_FAILED(rv)) return rv;
+ }
+ // else, mBuffer already contains all of the data (left over from a
+ // previous block-file read or write).
+
+ NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
+
+ // create a new input stream
+ nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
+ if (!inStream) return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*inputStream = inStream);
+ return NS_OK;
+}
+
+
+// NOTE: called with service lock held
+nsresult
+nsDiskCacheStreamIO::GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream)
+{
+ NS_ENSURE_ARG_POINTER(outputStream);
+ *outputStream = nullptr;
+
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ASSERTION(!mOutputStreamIsOpen, "already have an output stream open");
+ NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
+ if (mOutputStreamIsOpen || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
+
+ mStreamEnd = mBinding->mCacheEntry->DataSize();
+
+ // Inits file or buffer and truncate at the desired offset
+ nsresult rv = SeekAndTruncate(offset);
+ if (NS_FAILED(rv)) return rv;
+
+ mOutputStreamIsOpen = true;
+ NS_ADDREF(*outputStream = this);
+ return NS_OK;
+}
+
+nsresult
+nsDiskCacheStreamIO::ClearBinding()
+{
+ nsresult rv = NS_OK;
+ if (mBinding && mOutputStreamIsOpen)
+ rv = CloseOutputStream();
+ mBinding = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Close()
+{
+ if (!mOutputStreamIsOpen) return NS_OK;
+
+ // grab service lock
+ nsCacheServiceAutoLock lock;
+
+ if (!mBinding) { // if we're severed, just clear member variables
+ mOutputStreamIsOpen = false;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CloseOutputStream();
+ if (NS_FAILED(rv))
+ NS_WARNING("CloseOutputStream() failed");
+
+ return rv;
+}
+
+nsresult
+nsDiskCacheStreamIO::CloseOutputStream()
+{
+ NS_ASSERTION(mBinding, "oops");
+
+ CACHE_LOG_DEBUG(("CACHE: CloseOutputStream [%x doomed=%u]\n",
+ mBinding->mRecord.HashNumber(), mBinding->mDoomed));
+
+ // Mark outputstream as closed, even if saving the stream fails
+ mOutputStreamIsOpen = false;
+
+ // When writing to a file, just close the file
+ if (mFD) {
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ return NS_OK;
+ }
+
+ // write data to cache blocks, or flush mBuffer to file
+ NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "stream is bigger than buffer");
+
+ nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ nsresult rv = NS_OK;
+
+ // delete existing storage
+ if (record->DataLocationInitialized()) {
+ rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only call UpdateRecord when there is no data to write,
+ // because WriteDataCacheBlocks / FlushBufferToFile calls it.
+ if ((mStreamEnd == 0) && (!mBinding->mDoomed)) {
+ rv = cacheMap->UpdateRecord(record);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cacheMap->UpdateRecord() failed.");
+ return rv; // XXX doom cache entry
+ }
+ }
+ }
+
+ if (mStreamEnd == 0) return NS_OK; // nothing to write
+
+ // try to write to the cache blocks
+ rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WriteDataCacheBlocks() failed.");
+
+ // failed to store in cacheblocks, save as separate file
+ rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
+ if (mFD) {
+ UpdateFileSize();
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ else
+ NS_WARNING("no file descriptor");
+ }
+
+ return rv;
+}
+
+
+// assumptions:
+// only one thread writing at a time
+// never have both output and input streams open
+// OnDataSizeChanged() will have already been called to update entry->DataSize()
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Write( const char * buffer,
+ uint32_t count,
+ uint32_t * bytesWritten)
+{
+ NS_ENSURE_ARG_POINTER(buffer);
+ NS_ENSURE_ARG_POINTER(bytesWritten);
+ if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
+
+ *bytesWritten = 0; // always initialize to zero in case of errors
+
+ NS_ASSERTION(count, "Write called with count of zero");
+ if (count == 0) {
+ return NS_OK; // nothing to write
+ }
+
+ // grab service lock
+ nsCacheServiceAutoLock lock;
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mInStreamCount) {
+ // we have open input streams already
+ // this is an error until we support overlapped I/O
+ NS_WARNING("Attempting to write to cache entry with open input streams.\n");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Not writing to file, and it will fit in the cachedatablocks?
+ if (!mFD && (mStreamEnd + count <= kMaxBufferSize)) {
+
+ // We have more data than the current buffer size?
+ if ((mStreamEnd + count > mBufSize) && (mBufSize < kMaxBufferSize)) {
+ // Increase buffer to the maximum size.
+ mBuffer = (char *) moz_xrealloc(mBuffer, kMaxBufferSize);
+ mBufSize = kMaxBufferSize;
+ }
+
+ // Store in the buffer but only if it fits
+ if (mStreamEnd + count <= mBufSize) {
+ memcpy(mBuffer + mStreamEnd, buffer, count);
+ mStreamEnd += count;
+ *bytesWritten = count;
+ return NS_OK;
+ }
+ }
+
+ // There are more bytes than fit in the buffer/cacheblocks, switch to file
+ if (!mFD) {
+ // Opens a cache file and write the buffer to it
+ nsresult rv = FlushBufferToFile();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ // Write directly to the file
+ if (PR_Write(mFD, buffer, count) != (int32_t)count) {
+ NS_WARNING("failed to write all data");
+ return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
+ }
+ mStreamEnd += count;
+ *bytesWritten = count;
+
+ UpdateFileSize();
+ NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
+
+ return NS_OK;
+}
+
+
+void
+nsDiskCacheStreamIO::UpdateFileSize()
+{
+ NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
+
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ const uint32_t oldSizeK = record->DataFileSize();
+ uint32_t newSizeK = (mStreamEnd + 0x03FF) >> 10;
+
+ // make sure the size won't overflow (bug #651100)
+ if (newSizeK > kMaxDataSizeK)
+ newSizeK = kMaxDataSizeK;
+
+ if (newSizeK == oldSizeK) return;
+
+ record->SetDataFileSize(newSizeK);
+
+ // update cache size totals
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ cacheMap->DecrementTotalSize(oldSizeK); // decrement old size
+ cacheMap->IncrementTotalSize(newSizeK); // increment new size
+
+ if (!mBinding->mDoomed) {
+ nsresult rv = cacheMap->UpdateRecord(record);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cacheMap->UpdateRecord() failed.");
+ // XXX doom cache entry?
+ }
+ }
+}
+
+
+nsresult
+nsDiskCacheStreamIO::OpenCacheFile(int flags, PRFileDesc ** fd)
+{
+ NS_ENSURE_ARG_POINTER(fd);
+
+ CACHE_LOG_DEBUG(("nsDiskCacheStreamIO::OpenCacheFile"));
+
+ nsresult rv;
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ nsCOMPtr<nsIFile> localFile;
+
+ rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
+ nsDiskCache::kData,
+ !!(flags & PR_CREATE_FILE),
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return rv;
+
+ // create PRFileDesc for input stream - the 00600 is just for consistency
+ return localFile->OpenNSPRFileDesc(flags, 00600, fd);
+}
+
+
+nsresult
+nsDiskCacheStreamIO::ReadCacheBlocks(uint32_t bufferSize)
+{
+ NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
+ NS_ASSERTION(bufferSize <= kMaxBufferSize, "bufferSize too large for buffer");
+ NS_ASSERTION(mStreamEnd <= bufferSize, "data too large for buffer");
+
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+ if (!record->DataLocationInitialized()) return NS_OK;
+
+ NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
+
+ if (!mBuffer) {
+ mBuffer = (char *) moz_xmalloc(bufferSize);
+ mBufSize = bufferSize;
+ }
+
+ // read data stored in cache block files
+ nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference
+ return map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
+}
+
+
+nsresult
+nsDiskCacheStreamIO::FlushBufferToFile()
+{
+ nsresult rv;
+ nsDiskCacheRecord * record = &mBinding->mRecord;
+
+ if (!mFD) {
+ if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
+ // remove cache block storage
+ nsDiskCacheMap * cacheMap = mDevice->CacheMap();
+ rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
+ if (NS_FAILED(rv)) return rv;
+ }
+ record->SetDataFileGeneration(mBinding->mGeneration);
+
+ // allocate file
+ rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t dataSize = mBinding->mCacheEntry->PredictedDataSize();
+ if (dataSize != -1)
+ mozilla::fallocate(mFD, std::min<int64_t>(dataSize, kPreallocateLimit));
+ }
+
+ // write buffer to the file when there is data in it
+ if (mStreamEnd > 0) {
+ if (!mBuffer) {
+ NS_RUNTIMEABORT("Fix me!");
+ }
+ if (PR_Write(mFD, mBuffer, mStreamEnd) != (int32_t)mStreamEnd) {
+ NS_WARNING("failed to flush all data");
+ return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
+ }
+ }
+
+ // buffer is no longer valid
+ DeleteBuffer();
+
+ return NS_OK;
+}
+
+
+void
+nsDiskCacheStreamIO::DeleteBuffer()
+{
+ if (mBuffer) {
+ free(mBuffer);
+ mBuffer = nullptr;
+ mBufSize = 0;
+ }
+}
+
+size_t
+nsDiskCacheStreamIO::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ size_t usage = aMallocSizeOf(this);
+
+ usage += aMallocSizeOf(mFD);
+ usage += aMallocSizeOf(mBuffer);
+
+ return usage;
+}
+
+nsresult
+nsDiskCacheStreamIO::SeekAndTruncate(uint32_t offset)
+{
+ if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
+
+ if (uint32_t(offset) > mStreamEnd) return NS_ERROR_FAILURE;
+
+ // Set the current end to the desired offset
+ mStreamEnd = offset;
+
+ // Currently stored in file?
+ if (mBinding->mRecord.DataLocationInitialized() &&
+ (mBinding->mRecord.DataFile() == 0)) {
+ if (!mFD) {
+ // we need an mFD, we better open it now
+ nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (offset) {
+ if (PR_Seek(mFD, offset, PR_SEEK_SET) == -1)
+ return NS_ErrorAccordingToNSPR();
+ }
+ nsDiskCache::Truncate(mFD, offset);
+ UpdateFileSize();
+
+ // When we starting at zero again, close file and start with buffer.
+ // If offset is non-zero (and within buffer) an option would be
+ // to read the file into the buffer, but chance is high that it is
+ // rewritten to the file anyway.
+ if (offset == 0) {
+ // close file descriptor
+ (void) PR_Close(mFD);
+ mFD = nullptr;
+ }
+ return NS_OK;
+ }
+
+ // read data into mBuffer if not read yet.
+ if (offset && !mBuffer) {
+ nsresult rv = ReadCacheBlocks(kMaxBufferSize);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // stream buffer sanity check
+ NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "bad stream");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::Flush()
+{
+ if (!mOutputStreamIsOpen) return NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteFrom(nsIInputStream *inStream, uint32_t count, uint32_t *bytesWritten)
+{
+ NS_NOTREACHED("WriteFrom");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::WriteSegments( nsReadSegmentFun reader,
+ void * closure,
+ uint32_t count,
+ uint32_t * bytesWritten)
+{
+ NS_NOTREACHED("WriteSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsDiskCacheStreamIO::IsNonBlocking(bool * nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDiskCacheStreams.h b/netwerk/cache/nsDiskCacheStreams.h
new file mode 100644
index 000000000..247c16a6e
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheStreams.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef _nsDiskCacheStreams_h_
+#define _nsDiskCacheStreams_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nsDiskCacheBinding.h"
+
+#include "nsCache.h"
+
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+#include "mozilla/Atomics.h"
+
+class nsDiskCacheDevice;
+
+class nsDiskCacheStreamIO : public nsIOutputStream {
+public:
+ explicit nsDiskCacheStreamIO(nsDiskCacheBinding * binding);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult GetInputStream(uint32_t offset, nsIInputStream ** inputStream);
+ nsresult GetOutputStream(uint32_t offset, nsIOutputStream ** outputStream);
+
+ nsresult ClearBinding();
+
+ void IncrementInputStreamCount() { mInStreamCount++; }
+ void DecrementInputStreamCount()
+ {
+ mInStreamCount--;
+ NS_ASSERTION(mInStreamCount >= 0, "mInStreamCount has gone negative");
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ // GCC 2.95.2 requires this to be defined, although we never call it.
+ // and OS/2 requires that it not be private
+ nsDiskCacheStreamIO() { NS_NOTREACHED("oops"); }
+
+private:
+ virtual ~nsDiskCacheStreamIO();
+
+ nsresult OpenCacheFile(int flags, PRFileDesc ** fd);
+ nsresult ReadCacheBlocks(uint32_t bufferSize);
+ nsresult FlushBufferToFile();
+ void UpdateFileSize();
+ void DeleteBuffer();
+ nsresult CloseOutputStream();
+ nsresult SeekAndTruncate(uint32_t offset);
+
+ nsDiskCacheBinding * mBinding; // not an owning reference
+ nsDiskCacheDevice * mDevice;
+ mozilla::Atomic<int32_t> mInStreamCount;
+ PRFileDesc * mFD;
+
+ uint32_t mStreamEnd; // current size of data
+ uint32_t mBufSize; // current end of buffer
+ char * mBuffer;
+ bool mOutputStreamIsOpen;
+};
+
+#endif // _nsDiskCacheStreams_h_
diff --git a/netwerk/cache/nsICache.idl b/netwerk/cache/nsICache.idl
new file mode 100644
index 000000000..95bab1515
--- /dev/null
+++ b/netwerk/cache/nsICache.idl
@@ -0,0 +1,142 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+typedef long nsCacheStoragePolicy;
+typedef long nsCacheAccessMode;
+
+/**
+ * nsICache is a namespace for various cache constants. It does not represent
+ * an actual object.
+ */
+[scriptable, uuid(d6c67f38-b39a-4582-8a48-4c4f8a56dfd0)]
+interface nsICache
+{
+ /**
+ * Access Modes
+ *
+ *
+ * Mode Requested | Not Cached | Cached
+ * ------------------------------------------------------------------------
+ * READ | KEY_NOT_FOUND | NS_OK
+ * | Mode = NONE | Mode = READ
+ * | No Descriptor | Descriptor
+ * ------------------------------------------------------------------------
+ * WRITE | NS_OK | NS_OK (Cache service
+ * | Mode = WRITE | Mode = WRITE dooms existing
+ * | Descriptor | Descriptor cache entry)
+ * ------------------------------------------------------------------------
+ * READ_WRITE | NS_OK | NS_OK
+ * (1st req.) | Mode = WRITE | Mode = READ_WRITE
+ * | Descriptor | Descriptor
+ * ------------------------------------------------------------------------
+ * READ_WRITE | N/A | NS_OK
+ * (Nth req.) | | Mode = READ
+ * | | Descriptor
+ * ------------------------------------------------------------------------
+ *
+ *
+ * Access Requested:
+ *
+ * READ - I only want to READ, if there isn't an entry just fail
+ * WRITE - I have something new I want to write into the cache, make
+ * me a new entry and doom the old one, if any.
+ * READ_WRITE - I want to READ, but I'm willing to update an existing
+ * entry if necessary, or create a new one if none exists.
+ *
+ *
+ * Access Granted:
+ *
+ * NONE - No descriptor is provided. You get zilch. Nada. Nothing.
+ * READ - You can READ from this descriptor.
+ * WRITE - You must WRITE to this descriptor because the cache entry
+ * was just created for you.
+ * READ_WRITE - You can READ the descriptor to determine if it's valid,
+ * you may WRITE if it needs updating.
+ *
+ *
+ * Comments:
+ *
+ * If you think that you might need to modify cached data or meta data,
+ * then you must open a cache entry requesting WRITE access. Only one
+ * cache entry descriptor, per cache entry, will be granted WRITE access.
+ *
+ * Usually, you will request READ_WRITE access in order to first test the
+ * meta data and informational fields to determine if a write (ie. going
+ * to the net) may actually be necessary. If you determine that it is
+ * not, then you would mark the cache entry as valid (using MarkValid) and
+ * then simply read the data from the cache.
+ *
+ * A descriptor granted WRITE access has exclusive access to the cache
+ * entry up to the point at which it marks it as valid. Once the cache
+ * entry has been "validated", other descriptors with READ access may be
+ * opened to the cache entry.
+ *
+ * If you make a request for READ_WRITE access to a cache entry, the cache
+ * service will downgrade your access to READ if there is already a
+ * cache entry descriptor open with WRITE access.
+ *
+ * If you make a request for only WRITE access to a cache entry and another
+ * descriptor with WRITE access is currently open, then the existing cache
+ * entry will be 'doomed', and you will be given a descriptor (with WRITE
+ * access only) to a new cache entry.
+ *
+ */
+ const nsCacheAccessMode ACCESS_NONE = 0;
+ const nsCacheAccessMode ACCESS_READ = 1;
+ const nsCacheAccessMode ACCESS_WRITE = 2;
+ const nsCacheAccessMode ACCESS_READ_WRITE = 3;
+
+ /**
+ * Storage Policy
+ *
+ * The storage policy of a cache entry determines the device(s) to which
+ * it belongs. See nsICacheSession and nsICacheEntryDescriptor for more
+ * details.
+ *
+ * STORE_ANYWHERE - Allows the cache entry to be stored in any device.
+ * The cache service decides which cache device to use
+ * based on "some resource management calculation."
+ * STORE_IN_MEMORY - Requires the cache entry to reside in non-persistent
+ * storage (ie. typically in system RAM).
+ * STORE_ON_DISK - Requires the cache entry to reside in persistent
+ * storage (ie. typically on a system's hard disk).
+ * STORE_OFFLINE - Requires the cache entry to reside in persistent,
+ * reliable storage for offline use.
+ */
+ const nsCacheStoragePolicy STORE_ANYWHERE = 0;
+ const nsCacheStoragePolicy STORE_IN_MEMORY = 1;
+ const nsCacheStoragePolicy STORE_ON_DISK = 2;
+ // value 3 was used by STORE_ON_DISK_AS_FILE which was removed
+ const nsCacheStoragePolicy STORE_OFFLINE = 4;
+
+ /**
+ * All entries for a cache session are stored as streams of data or
+ * as objects. These constant my be used to specify the type of entries
+ * when calling nsICacheService::CreateSession().
+ */
+ const long NOT_STREAM_BASED = 0;
+ const long STREAM_BASED = 1;
+
+ /**
+ * The synchronous OpenCacheEntry() may be blocking or non-blocking. If a cache entry is
+ * waiting to be validated by another cache descriptor (so no new cache descriptors for that
+ * key can be created, OpenCacheEntry() will return NS_ERROR_CACHE_WAIT_FOR_VALIDATION in
+ * non-blocking mode. In blocking mode, it will wait until the cache entry for the key has
+ * been validated or doomed. If the cache entry is validated, then a descriptor for that
+ * entry will be created and returned. If the cache entry was doomed, then a descriptor
+ * will be created for a new cache entry for the key.
+ */
+ const long NON_BLOCKING = 0;
+ const long BLOCKING = 1;
+
+ /**
+ * Constant meaning no expiration time.
+ */
+ const unsigned long NO_EXPIRATION_TIME = 0xFFFFFFFF;
+};
+
diff --git a/netwerk/cache/nsICacheEntryDescriptor.idl b/netwerk/cache/nsICacheEntryDescriptor.idl
new file mode 100644
index 000000000..20fac747d
--- /dev/null
+++ b/netwerk/cache/nsICacheEntryDescriptor.idl
@@ -0,0 +1,164 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICacheVisitor.idl"
+#include "nsICache.idl"
+
+interface nsISimpleEnumerator;
+interface nsICacheListener;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIFile;
+interface nsICacheMetaDataVisitor;
+
+
+[scriptable, uuid(90b17d31-46aa-4fb1-a206-473c966cbc18)]
+interface nsICacheEntryDescriptor : nsICacheEntryInfo
+{
+ /**
+ * Set the time at which the cache entry should be considered invalid (in
+ * seconds since the Epoch).
+ */
+ void setExpirationTime(in uint32_t expirationTime);
+
+ /**
+ * Set the cache entry data size. This will fail if the cache entry
+ * IS stream based.
+ */
+ void setDataSize(in unsigned long size);
+
+ /**
+ * Open blocking input stream to cache data. This will fail if the cache
+ * entry IS NOT stream based. Use the stream transport service to
+ * asynchronously read this stream on a background thread. The returned
+ * stream MAY implement nsISeekableStream.
+ *
+ * @param offset
+ * read starting from this offset into the cached data. an offset
+ * beyond the end of the stream has undefined consequences.
+ *
+ * @return blocking, unbuffered input stream.
+ */
+ nsIInputStream openInputStream(in unsigned long offset);
+
+ /**
+ * Open blocking output stream to cache data. This will fail if the cache
+ * entry IS NOT stream based. Use the stream transport service to
+ * asynchronously write to this stream on a background thread. The returned
+ * stream MAY implement nsISeekableStream.
+ *
+ * If opening an output stream to existing cached data, the data will be
+ * truncated to the specified offset.
+ *
+ * @param offset
+ * write starting from this offset into the cached data. an offset
+ * beyond the end of the stream has undefined consequences.
+ *
+ * @return blocking, unbuffered output stream.
+ */
+ nsIOutputStream openOutputStream(in unsigned long offset);
+
+ /**
+ * Get/set the cache data element. This will fail if the cache entry
+ * IS stream based. The cache entry holds a strong reference to this
+ * object. The object will be released when the cache entry is destroyed.
+ */
+ attribute nsISupports cacheElement;
+
+ /**
+ * Stores the Content-Length specified in the HTTP header for this
+ * entry. Checked before we write to the cache entry, to prevent ever
+ * taking up space in the cache for an entry that we know up front
+ * is going to have to be evicted anyway. See bug 588507.
+ */
+ attribute int64_t predictedDataSize;
+
+ /**
+ * Get the access granted to this descriptor. See nsICache.idl for the
+ * definitions of the access modes and a thorough description of their
+ * corresponding meanings.
+ */
+ readonly attribute nsCacheAccessMode accessGranted;
+
+ /**
+ * Get/set the storage policy of the cache entry. See nsICache.idl for
+ * the definitions of the storage policies.
+ */
+ attribute nsCacheStoragePolicy storagePolicy;
+
+ /**
+ * Get the disk file associated with the cache entry.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Get/set security info on the cache entry for this descriptor. This fails
+ * if the storage policy is not STORE_IN_MEMORY.
+ */
+ attribute nsISupports securityInfo;
+
+ /**
+ * Get the size of the cache entry data, as stored. This may differ
+ * from the entry's dataSize, if the entry is compressed.
+ */
+ readonly attribute unsigned long storageDataSize;
+
+ /**
+ * Doom the cache entry this descriptor references in order to slate it for
+ * removal. Once doomed a cache entry cannot be undoomed.
+ *
+ * A descriptor with WRITE access can doom the cache entry and choose to
+ * fail pending requests. This means that pending requests will not get
+ * a cache descriptor. This is meant as a tool for clients that wish to
+ * instruct pending requests to skip the cache.
+ */
+ void doom();
+ void doomAndFailPendingRequests(in nsresult status);
+
+ /**
+ * Asynchronously doom an entry. Listener will be notified about the status
+ * of the operation. Null may be passed if caller doesn't care about the
+ * result.
+ */
+ void asyncDoom(in nsICacheListener listener);
+
+ /**
+ * A writer must validate this cache object before any readers are given
+ * a descriptor to the object.
+ */
+ void markValid();
+
+ /**
+ * Explicitly close the descriptor (optional).
+ */
+
+ void close();
+
+ /**
+ * Methods for accessing meta data. Meta data is a table of key/value
+ * string pairs. The strings do not have to conform to any particular
+ * charset, but they must be null terminated.
+ */
+ string getMetaDataElement(in string key);
+ void setMetaDataElement(in string key, in string value);
+
+ /**
+ * Visitor will be called with key/value pair for each meta data element.
+ */
+ void visitMetaData(in nsICacheMetaDataVisitor visitor);
+};
+
+
+
+[scriptable, uuid(22f9a49c-3cf8-4c23-8006-54efb11ac562)]
+interface nsICacheMetaDataVisitor : nsISupports
+{
+ /**
+ * Called for each key/value pair in the meta data for a cache entry
+ */
+ boolean visitMetaDataElement(in string key,
+ in string value);
+};
diff --git a/netwerk/cache/nsICacheListener.idl b/netwerk/cache/nsICacheListener.idl
new file mode 100644
index 000000000..cfb2dd470
--- /dev/null
+++ b/netwerk/cache/nsICacheListener.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+#include "nsICache.idl"
+
+
+interface nsICacheEntryDescriptor;
+
+[scriptable, uuid(8eadf2ed-8cac-4961-8025-6da6d5827e74)]
+interface nsICacheListener : nsISupports
+{
+ /**
+ * Called when the requested access (or appropriate subset) is
+ * acquired. The status parameter equals NS_OK on success.
+ * See nsICacheService.idl for accessGranted values.
+ */
+ void onCacheEntryAvailable(in nsICacheEntryDescriptor descriptor,
+ in nsCacheAccessMode accessGranted,
+ in nsresult status);
+
+ /**
+ * Called when nsCacheSession::DoomEntry() is completed. The status
+ * parameter is NS_OK when the entry was doomed, or NS_ERROR_NOT_AVAILABLE
+ * when there is no such entry.
+ */
+ void onCacheEntryDoomed(in nsresult status);
+};
diff --git a/netwerk/cache/nsICacheService.idl b/netwerk/cache/nsICacheService.idl
new file mode 100644
index 000000000..b015a7244
--- /dev/null
+++ b/netwerk/cache/nsICacheService.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsICache.idl"
+
+interface nsISimpleEnumerator;
+interface nsICacheListener;
+interface nsICacheSession;
+interface nsICacheVisitor;
+interface nsIEventTarget;
+
+/**
+ * @deprecated
+ *
+ * IMPORTANT NOTE: THIS INTERFACE IS NO LONGER SUPPORTED AND PLANNED TO BE
+ * REMOVED SOON. WE STRONGLY ENCORAGE TO MIGRATE THE EXISTING CODE AND FOR
+ * THE NEW CODE USE ONLY THE NEW HTTP CACHE API IN netwerk/cache2/.
+ */
+[scriptable, uuid(14dbe1e9-f3bc-45af-92f4-2c574fcd4e39)]
+interface nsICacheService : nsISupports
+{
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Create a cache session
+ *
+ * A cache session represents a client's access into the cache. The cache
+ * session is not "owned" by the cache service. Hence, it is possible to
+ * create duplicate cache sessions. Entries created by a cache session
+ * are invisible to other cache sessions, unless the cache sessions are
+ * equivalent.
+ *
+ * @param clientID - Specifies the name of the client using the cache.
+ * @param storagePolicy - Limits the storage policy for all entries
+ * accessed via the returned session. As a result, devices excluded
+ * by the storage policy will not be searched when opening entries
+ * from the returned session.
+ * @param streamBased - Indicates whether or not the data being cached
+ * can be represented as a stream. The storagePolicy must be
+ * consistent with the value of this field. For example, a non-stream-
+ * based cache entry can only have a storage policy of STORE_IN_MEMORY.
+ * @return new cache session.
+ */
+ nsICacheSession createSession(in string clientID,
+ in nsCacheStoragePolicy storagePolicy,
+ in boolean streamBased);
+
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Visit entries stored in the cache. Used to implement about:cache.
+ */
+ void visitEntries(in nsICacheVisitor visitor);
+
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Evicts all entries in all devices implied by the storage policy.
+ *
+ * @note This function may evict some items but will throw if it fails to evict
+ * everything.
+ */
+ void evictEntries(in nsCacheStoragePolicy storagePolicy);
+
+ /**
+ * Event target which is used for I/O operations
+ */
+ readonly attribute nsIEventTarget cacheIOTarget;
+};
+
+%{C++
+/**
+ * Observer service notification that is sent when
+ * nsICacheService::evictEntries() or nsICacheSession::evictEntries()
+ * is called.
+ */
+#define NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID "cacheservice:empty-cache"
+%}
+
+[scriptable, builtinclass, uuid(d0fc8d38-db80-4928-bf1c-b0085ddfa9dc)]
+interface nsICacheServiceInternal : nsICacheService
+{
+ /**
+ * This is an internal interface. It changes so frequently that it probably
+ * went away while you were reading this.
+ */
+
+ /**
+ * Milliseconds for which the service lock has been held. 0 if unlocked.
+ */
+ readonly attribute double lockHeldTime;
+};
+
+
diff --git a/netwerk/cache/nsICacheSession.idl b/netwerk/cache/nsICacheSession.idl
new file mode 100644
index 000000000..e2a4dec5b
--- /dev/null
+++ b/netwerk/cache/nsICacheSession.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsICache.idl"
+
+interface nsICacheEntryDescriptor;
+interface nsICacheListener;
+interface nsIFile;
+
+[scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)]
+interface nsICacheSession : nsISupports
+{
+ /**
+ * Expired entries will be doomed or evicted if this attribute is set to
+ * true. If false, expired entries will be returned (useful for offline-
+ * mode and clients, such as HTTP, that can update the valid lifetime of
+ * cached content). This attribute defaults to true.
+ */
+ attribute boolean doomEntriesIfExpired;
+
+ /**
+ * When set, entries created with this session will be placed to a cache
+ * based at this directory. Use when storing entries to a different
+ * profile than the active profile of the the current running application
+ * process.
+ */
+ attribute nsIFile profileDirectory;
+
+ /**
+ * A cache session can only give out one descriptor with WRITE access
+ * to a given cache entry at a time. Until the client calls MarkValid on
+ * its descriptor, other attempts to open the same cache entry will block.
+ */
+
+ /**
+ * Synchronous cache access. This method fails if it is called on the main
+ * thread. Use asyncOpenCacheEntry() instead. This returns a unique
+ * descriptor each time it is called, even if the same key is specified.
+ * When called by multiple threads for write access, only one writable
+ * descriptor will be granted. If 'blockingMode' is set to false, it will
+ * return NS_ERROR_CACHE_WAIT_FOR_VALIDATION rather than block when another
+ * descriptor has been given WRITE access but hasn't validated the entry yet.
+ */
+ nsICacheEntryDescriptor openCacheEntry(in ACString key,
+ in nsCacheAccessMode accessRequested,
+ in boolean blockingMode);
+
+ /**
+ * Asynchronous cache access. Does not block the calling thread. Instead,
+ * the listener will be notified when the descriptor is available. If
+ * 'noWait' is set to true, the listener will be notified immediately with
+ * status NS_ERROR_CACHE_WAIT_FOR_VALIDATION rather than queuing the request
+ * when another descriptor has been given WRITE access but hasn't validated
+ * the entry yet.
+ */
+ void asyncOpenCacheEntry(in ACString key,
+ in nsCacheAccessMode accessRequested,
+ in nsICacheListener listener,
+ [optional] in boolean noWait);
+
+ /**
+ * Evict all entries for this session's clientID according to its storagePolicy.
+ */
+ void evictEntries();
+
+ /**
+ * Return whether any of the cache devices implied by the session storage policy
+ * are currently enabled for instantiation if they don't already exist.
+ */
+ boolean isStorageEnabled();
+
+ /**
+ * Asynchronously doom an entry specified by the key. Listener will be
+ * notified about the status of the operation. Null may be passed if caller
+ * doesn't care about the result.
+ */
+ void doomEntry(in ACString key, in nsICacheListener listener);
+
+ /**
+ * Private entries will be doomed when the last private browsing session
+ * finishes.
+ */
+ attribute boolean isPrivate;
+};
diff --git a/netwerk/cache/nsICacheVisitor.idl b/netwerk/cache/nsICacheVisitor.idl
new file mode 100644
index 000000000..ddc993635
--- /dev/null
+++ b/netwerk/cache/nsICacheVisitor.idl
@@ -0,0 +1,123 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/* XXX we should define device and entry info as well (stats, etc) */
+
+interface nsICacheDeviceInfo;
+interface nsICacheEntryInfo;
+
+
+[scriptable, uuid(f8c08c4b-d778-49d1-a59b-866fdc500d95)]
+interface nsICacheVisitor : nsISupports
+{
+ /**
+ * Called to provide information about a cache device.
+ *
+ * @param deviceID - specifies the device being visited.
+ * @param deviceInfo - specifies information about this device.
+ *
+ * @return true to start visiting all entries for this device.
+ * @return false to advance to the next device.
+ */
+ boolean visitDevice(in string deviceID,
+ in nsICacheDeviceInfo deviceInfo);
+
+ /**
+ * Called to provide information about a cache entry.
+ *
+ * @param deviceID - specifies the device being visited.
+ * @param entryInfo - specifies information about this entry.
+ *
+ * @return true to visit the next entry on the current device, or if the
+ * end of the device has been reached, advance to the next device.
+ * @return false to advance to the next device.
+ */
+ boolean visitEntry(in string deviceID,
+ in nsICacheEntryInfo entryInfo);
+};
+
+
+[scriptable, uuid(31d1c294-1dd2-11b2-be3a-c79230dca297)]
+interface nsICacheDeviceInfo : nsISupports
+{
+ /**
+ * Get a human readable description of the cache device.
+ */
+ readonly attribute string description;
+
+ /**
+ * Get a usage report, statistics, miscellaneous data about
+ * the cache device.
+ */
+ readonly attribute string usageReport;
+
+ /**
+ * Get the number of stored cache entries.
+ */
+ readonly attribute unsigned long entryCount;
+
+ /**
+ * Get the total size of the stored cache entries.
+ */
+ readonly attribute unsigned long totalSize;
+
+ /**
+ * Get the upper limit of the size of the data the cache can store.
+ */
+ readonly attribute unsigned long maximumSize;
+};
+
+
+[scriptable, uuid(fab51c92-95c3-4468-b317-7de4d7588254)]
+interface nsICacheEntryInfo : nsISupports
+{
+ /**
+ * Get the client id associated with this cache entry.
+ */
+ readonly attribute string clientID;
+
+ /**
+ * Get the id for the device that stores this cache entry.
+ */
+ readonly attribute string deviceID;
+
+ /**
+ * Get the key identifying the cache entry.
+ */
+ readonly attribute ACString key;
+
+ /**
+ * Get the number of times the cache entry has been opened.
+ */
+ readonly attribute long fetchCount;
+
+ /**
+ * Get the last time the cache entry was opened (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t lastFetched;
+
+ /**
+ * Get the last time the cache entry was modified (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t lastModified;
+
+ /**
+ * Get the expiration time of the cache entry (in seconds since the Epoch).
+ */
+ readonly attribute uint32_t expirationTime;
+
+ /**
+ * Get the cache entry data size.
+ */
+ readonly attribute unsigned long dataSize;
+
+ /**
+ * Find out whether or not the cache entry is stream based.
+ */
+ boolean isStreamBased();
+};
diff --git a/netwerk/cache/nsMemoryCacheDevice.cpp b/netwerk/cache/nsMemoryCacheDevice.cpp
new file mode 100644
index 000000000..042e86022
--- /dev/null
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCache.h"
+#include "nsMemoryCacheDevice.h"
+#include "nsCacheService.h"
+#include "nsICacheService.h"
+#include "nsICacheVisitor.h"
+#include "nsIStorageStream.h"
+#include "nsCRT.h"
+#include "nsReadableUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Telemetry.h"
+#include <algorithm>
+
+// The memory cache implements the "LRU-SP" caching algorithm
+// described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
+// Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
+
+// We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
+// The queues hold exponentially increasing ranges of floor(log2((size/nref)))
+// values for entries.
+// Entries larger than 2^(kQueueCount-1) go in the last queue.
+// Entries with no expiration go in the first queue.
+
+const char *gMemoryDeviceID = "memory";
+using namespace mozilla;
+
+nsMemoryCacheDevice::nsMemoryCacheDevice()
+ : mInitialized(false),
+ mHardLimit(4 * 1024 * 1024), // default, if no pref
+ mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
+ mTotalSize(0),
+ mInactiveSize(0),
+ mEntryCount(0),
+ mMaxEntryCount(0),
+ mMaxEntrySize(-1) // -1 means "no limit"
+{
+ for (int i=0; i<kQueueCount; ++i)
+ PR_INIT_CLIST(&mEvictionList[i]);
+}
+
+
+nsMemoryCacheDevice::~nsMemoryCacheDevice()
+{
+ Shutdown();
+}
+
+
+nsresult
+nsMemoryCacheDevice::Init()
+{
+ if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
+
+ mMemCacheEntries.Init();
+ mInitialized = true;
+ return NS_OK;
+}
+
+
+nsresult
+nsMemoryCacheDevice::Shutdown()
+{
+ NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized");
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ mMemCacheEntries.Shutdown();
+
+ // evict all entries
+ nsCacheEntry * entry, * next;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+ while (entry != &mEvictionList[i]) {
+ NS_ASSERTION(!entry->IsInUse(), "### shutting down with active entries");
+ next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ PR_REMOVE_AND_INIT_LINK(entry);
+
+ // update statistics
+ int32_t memoryRecovered = (int32_t)entry->DataSize();
+ mTotalSize -= memoryRecovered;
+ mInactiveSize -= memoryRecovered;
+ --mEntryCount;
+
+ delete entry;
+ entry = next;
+ }
+ }
+
+/*
+ * we're not factoring in changes to meta data yet...
+ * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?");
+ */
+ NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?");
+ NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?");
+
+ mInitialized = false;
+
+ return NS_OK;
+}
+
+
+const char *
+nsMemoryCacheDevice::GetDeviceID()
+{
+ return gMemoryDeviceID;
+}
+
+
+nsCacheEntry *
+nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision)
+{
+ mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_MEMORY_SEARCH_2> timer;
+ nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
+ if (!entry) return nullptr;
+
+ // move entry to the tail of an eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
+
+ mInactiveSize -= entry->DataSize();
+
+ return entry;
+}
+
+
+nsresult
+nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
+ entry));
+ if (entry->IsDoomed()) {
+#ifdef DEBUG
+ // XXX verify we've removed it from mMemCacheEntries & eviction list
+#endif
+ delete entry;
+ CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry));
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
+ NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
+ NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
+ if (ourEntry != entry)
+ return NS_ERROR_INVALID_POINTER;
+#endif
+
+ mInactiveSize += entry->DataSize();
+ EvictEntriesIfNecessary();
+
+ return NS_OK;
+}
+
+
+nsresult
+nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
+{
+ if (!entry->IsDoomed()) {
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
+
+ // append entry to the eviction list
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
+
+ // add entry to hashtable of mem cache entries
+ nsresult rv = mMemCacheEntries.AddEntry(entry);
+ if (NS_FAILED(rv)) {
+ PR_REMOVE_AND_INIT_LINK(entry);
+ return rv;
+ }
+
+ // add size of entry to memory totals
+ ++mEntryCount;
+ if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
+
+ mTotalSize += entry->DataSize();
+ EvictEntriesIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
+{
+#ifdef DEBUG
+ // debug code to verify we have entry
+ nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key());
+ if (!hashEntry) NS_WARNING("no entry for key");
+ else if (entry != hashEntry) NS_WARNING("entry != hashEntry");
+#endif
+ CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry));
+ EvictEntry(entry, DO_NOT_DELETE_ENTRY);
+}
+
+
+nsresult
+nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIStorageStream> storage;
+ nsresult rv;
+
+ nsISupports *data = entry->Data();
+ if (data) {
+ storage = do_QueryInterface(data, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
+ if (NS_FAILED(rv))
+ return rv;
+ entry->SetData(storage);
+ }
+
+ return storage->NewInputStream(offset, result);
+}
+
+
+nsresult
+nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(entry);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCOMPtr<nsIStorageStream> storage;
+ nsresult rv;
+
+ nsISupports *data = entry->Data();
+ if (data) {
+ storage = do_QueryInterface(data, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
+ if (NS_FAILED(rv))
+ return rv;
+ entry->SetData(storage);
+ }
+
+ return storage->GetOutputStream(offset, result);
+}
+
+
+nsresult
+nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
+ nsIFile ** result )
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+bool
+nsMemoryCacheDevice::EntryIsTooBig(int64_t entrySize)
+{
+ CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig "
+ "[size=%d max=%d soft=%d]\n",
+ entrySize, mMaxEntrySize, mSoftLimit));
+ if (mMaxEntrySize == -1)
+ return entrySize > mSoftLimit;
+ else
+ return (entrySize > mSoftLimit || entrySize > mMaxEntrySize);
+}
+
+size_t
+nsMemoryCacheDevice::TotalSize()
+{
+ return mTotalSize;
+}
+
+nsresult
+nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize)
+{
+ if (entry->IsStreamData()) {
+ // we have the right to refuse or pre-evict
+ uint32_t newSize = entry->DataSize() + deltaSize;
+ if (EntryIsTooBig(newSize)) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ // adjust our totals
+ mTotalSize += deltaSize;
+
+ if (!entry->IsDoomed()) {
+ // move entry to the tail of the appropriate eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
+ }
+
+ EvictEntriesIfNecessary();
+ return NS_OK;
+}
+
+
+void
+nsMemoryCacheDevice::AdjustMemoryLimits(int32_t softLimit, int32_t hardLimit)
+{
+ mSoftLimit = softLimit;
+ mHardLimit = hardLimit;
+
+ // First, evict entries that won't fit into the new cache size.
+ EvictEntriesIfNecessary();
+}
+
+
+void
+nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, bool deleteEntry)
+{
+ CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n",
+ entry, deleteEntry));
+ // remove entry from our hashtable
+ mMemCacheEntries.RemoveEntry(entry);
+
+ // remove entry from the eviction list
+ PR_REMOVE_AND_INIT_LINK(entry);
+
+ // update statistics
+ int32_t memoryRecovered = (int32_t)entry->DataSize();
+ mTotalSize -= memoryRecovered;
+ if (!entry->IsDoomed())
+ mInactiveSize -= memoryRecovered;
+ --mEntryCount;
+
+ if (deleteEntry) delete entry;
+}
+
+
+void
+nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
+{
+ nsCacheEntry * entry;
+ nsCacheEntry * maxEntry;
+ CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d,"
+ "mInactiveSize: %d, mSoftLimit: %d\n",
+ mTotalSize, mHardLimit, mInactiveSize, mSoftLimit));
+
+ if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
+ return;
+
+ uint32_t now = SecondsFromPRTime(PR_Now());
+ uint64_t entryCost = 0;
+ uint64_t maxCost = 0;
+ do {
+ // LRU-SP eviction selection: Check the head of each segment (each
+ // eviction list, kept in LRU order) and select the maximal-cost
+ // entry for eviction. Cost is time-since-accessed * size / nref.
+ maxEntry = 0;
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+
+ // If the head of a list is in use, check the next available entry
+ while ((entry != &mEvictionList[i]) &&
+ (entry->IsInUse())) {
+ entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ }
+
+ if (entry != &mEvictionList[i]) {
+ entryCost = (uint64_t)
+ (now - entry->LastFetched()) * entry->DataSize() /
+ std::max(1, entry->FetchCount());
+ if (!maxEntry || (entryCost > maxCost)) {
+ maxEntry = entry;
+ maxCost = entryCost;
+ }
+ }
+ }
+ if (maxEntry) {
+ EvictEntry(maxEntry, DELETE_ENTRY);
+ } else {
+ break;
+ }
+ }
+ while ((mTotalSize >= mHardLimit) || (mInactiveSize >= mSoftLimit));
+}
+
+
+int
+nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, int32_t deltaSize)
+{
+ // favor items which never expire by putting them in the lowest-index queue
+ if (entry->ExpirationTime() == nsICache::NO_EXPIRATION_TIME)
+ return 0;
+
+ // compute which eviction queue this entry should go into,
+ // based on floor(log2(size/nref))
+ int32_t size = deltaSize + (int32_t)entry->DataSize();
+ int32_t fetchCount = std::max(1, entry->FetchCount());
+
+ return std::min((int)mozilla::FloorLog2(size / fetchCount), kQueueCount - 1);
+}
+
+
+nsresult
+nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
+{
+ nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
+ nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
+ if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!keepGoing)
+ return NS_OK;
+
+ nsCacheEntry * entry;
+ nsCOMPtr<nsICacheEntryInfo> entryRef;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
+ while (entry != &mEvictionList[i]) {
+ nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
+ if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
+ entryRef = entryInfo;
+
+ rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
+ entryInfo->DetachEntry();
+ if (NS_FAILED(rv)) return rv;
+ if (!keepGoing) break;
+
+ entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ }
+ }
+ return NS_OK;
+}
+
+
+static bool
+IsEntryPrivate(nsCacheEntry* entry, void* args)
+{
+ return entry->IsPrivate();
+}
+
+struct ClientIDArgs {
+ const char* clientID;
+ uint32_t prefixLength;
+};
+
+static bool
+EntryMatchesClientID(nsCacheEntry* entry, void* args)
+{
+ const char * clientID = static_cast<ClientIDArgs*>(args)->clientID;
+ uint32_t prefixLength = static_cast<ClientIDArgs*>(args)->prefixLength;
+ const char * key = entry->Key()->get();
+ return !clientID || nsCRT::strncmp(clientID, key, prefixLength) == 0;
+}
+
+nsresult
+nsMemoryCacheDevice::DoEvictEntries(bool (*matchFn)(nsCacheEntry* entry, void* args), void* args)
+{
+ nsCacheEntry * entry;
+
+ for (int i = kQueueCount - 1; i >= 0; --i) {
+ PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
+ while (elem != &mEvictionList[i]) {
+ entry = (nsCacheEntry *)elem;
+ elem = PR_NEXT_LINK(elem);
+
+ if (!matchFn(entry, args))
+ continue;
+
+ if (entry->IsInUse()) {
+ nsresult rv = nsCacheService::DoomEntry(entry);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_WARNING(("memCache->DoEvictEntries() aborted: rv =%x", rv));
+ return rv;
+ }
+ } else {
+ EvictEntry(entry, DELETE_ENTRY);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMemoryCacheDevice::EvictEntries(const char * clientID)
+{
+ ClientIDArgs args = {clientID, clientID ? uint32_t(strlen(clientID)) : 0};
+ return DoEvictEntries(&EntryMatchesClientID, &args);
+}
+
+nsresult
+nsMemoryCacheDevice::EvictPrivateEntries()
+{
+ return DoEvictEntries(&IsEntryPrivate, nullptr);
+}
+
+
+// WARNING: SetCapacity can get called before Init()
+void
+nsMemoryCacheDevice::SetCapacity(int32_t capacity)
+{
+ int32_t hardLimit = capacity * 1024; // convert k into bytes
+ int32_t softLimit = (hardLimit * 9) / 10;
+ AdjustMemoryLimits(softLimit, hardLimit);
+}
+
+void
+nsMemoryCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
+{
+ // Internal unit is bytes. Changing this only takes effect *after* the
+ // change and has no consequences for existing cache-entries
+ if (maxSizeInKilobytes >= 0)
+ mMaxEntrySize = maxSizeInKilobytes * 1024;
+ else
+ mMaxEntrySize = -1;
+}
+
+#ifdef DEBUG
+void
+nsMemoryCacheDevice::CheckEntryCount()
+{
+ if (!mInitialized) return;
+
+ int32_t evictionListCount = 0;
+ for (int i=0; i<kQueueCount; ++i) {
+ PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
+ while (elem != &mEvictionList[i]) {
+ elem = PR_NEXT_LINK(elem);
+ ++evictionListCount;
+ }
+ }
+ NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness");
+
+ int32_t entryCount = 0;
+ for (auto iter = mMemCacheEntries.Iter(); !iter.Done(); iter.Next()) {
+ ++entryCount;
+ }
+ NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness");
+}
+#endif
+
+/******************************************************************************
+ * nsMemoryCacheDeviceInfo - for implementing about:cache
+ *****************************************************************************/
+
+
+NS_IMPL_ISUPPORTS(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo)
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetDescription(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = NS_strdup("Memory cache device");
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCString buffer;
+
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Inactive storage:</th>\n"
+ " <td>");
+ buffer.AppendInt(mDevice->mInactiveSize / 1024);
+ buffer.AppendLiteral(" KiB</td>\n"
+ " </tr>\n");
+
+ *result = ToNewCString(buffer);
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetEntryCount(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ // XXX compare calculated count vs. mEntryCount
+ *result = (uint32_t)mDevice->mEntryCount;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetTotalSize(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (uint32_t)mDevice->mTotalSize;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsMemoryCacheDeviceInfo::GetMaximumSize(uint32_t * result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (uint32_t)mDevice->mHardLimit;
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsMemoryCacheDevice.h b/netwerk/cache/nsMemoryCacheDevice.h
new file mode 100644
index 000000000..331b04ba6
--- /dev/null
+++ b/netwerk/cache/nsMemoryCacheDevice.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsMemoryCacheDevice_h_
+#define _nsMemoryCacheDevice_h_
+
+#include "nsCacheDevice.h"
+#include "PLDHashTable.h"
+#include "nsCacheEntry.h"
+
+
+class nsMemoryCacheDeviceInfo;
+
+/******************************************************************************
+ * nsMemoryCacheDevice
+ ******************************************************************************/
+class nsMemoryCacheDevice : public nsCacheDevice
+{
+public:
+ nsMemoryCacheDevice();
+ virtual ~nsMemoryCacheDevice();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual const char * GetDeviceID(void);
+
+ virtual nsresult BindEntry( nsCacheEntry * entry );
+ virtual nsCacheEntry * FindEntry( nsCString * key, bool *collision );
+ virtual void DoomEntry( nsCacheEntry * entry );
+ virtual nsresult DeactivateEntry( nsCacheEntry * entry );
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result);
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result);
+
+ virtual nsresult GetFileForEntry( nsCacheEntry * entry,
+ nsIFile ** result );
+
+ virtual nsresult OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize );
+
+ virtual nsresult Visit( nsICacheVisitor * visitor );
+
+ virtual nsresult EvictEntries(const char * clientID);
+ nsresult EvictPrivateEntries();
+
+ void SetCapacity(int32_t capacity);
+ void SetMaxEntrySize(int32_t maxSizeInKilobytes);
+
+ bool EntryIsTooBig(int64_t entrySize);
+
+ size_t TotalSize();
+
+private:
+ friend class nsMemoryCacheDeviceInfo;
+ enum { DELETE_ENTRY = true,
+ DO_NOT_DELETE_ENTRY = false };
+
+ void AdjustMemoryLimits( int32_t softLimit, int32_t hardLimit);
+ void EvictEntry( nsCacheEntry * entry , bool deleteEntry);
+ void EvictEntriesIfNecessary();
+ int EvictionList(nsCacheEntry * entry, int32_t deltaSize);
+
+ typedef bool (*EvictionMatcherFn)(nsCacheEntry* entry, void* args);
+ nsresult DoEvictEntries(EvictionMatcherFn matchFn, void* args);
+
+#ifdef DEBUG
+ void CheckEntryCount();
+#endif
+ /*
+ * Data members
+ */
+ enum {
+ kQueueCount = 24 // entries > 2^23 (8Mb) start in last queue
+ };
+
+ nsCacheEntryHashTable mMemCacheEntries;
+ bool mInitialized;
+
+ PRCList mEvictionList[kQueueCount];
+
+ int32_t mHardLimit;
+ int32_t mSoftLimit;
+
+ int32_t mTotalSize;
+ int32_t mInactiveSize;
+
+ int32_t mEntryCount;
+ int32_t mMaxEntryCount;
+ int32_t mMaxEntrySize; // internal unit is bytes
+
+ // XXX what other stats do we want to keep?
+};
+
+
+/******************************************************************************
+ * nsMemoryCacheDeviceInfo - used to call nsIVisitor for about:cache
+ ******************************************************************************/
+class nsMemoryCacheDeviceInfo : public nsICacheDeviceInfo {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsMemoryCacheDeviceInfo(nsMemoryCacheDevice* device)
+ : mDevice(device)
+ {
+ }
+
+private:
+ virtual ~nsMemoryCacheDeviceInfo() {}
+ nsMemoryCacheDevice* mDevice;
+};
+
+
+#endif // _nsMemoryCacheDevice_h_