import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

51
netwerk/cache/moz.build vendored Normal file
View file

@ -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']

29
netwerk/cache/nsApplicationCache.h vendored Normal file
View file

@ -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;
};

View file

@ -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);
}
}

View file

@ -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_

95
netwerk/cache/nsCache.cpp vendored Normal file
View file

@ -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;
}

42
netwerk/cache/nsCache.h vendored Normal file
View file

@ -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

63
netwerk/cache/nsCacheDevice.h vendored Normal file
View file

@ -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_

508
netwerk/cache/nsCacheEntry.cpp vendored Normal file
View file

@ -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;
}

302
netwerk/cache/nsCacheEntry.h vendored Normal file
View file

@ -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_

1475
netwerk/cache/nsCacheEntryDescriptor.cpp vendored Normal file

File diff suppressed because it is too large Load diff

237
netwerk/cache/nsCacheEntryDescriptor.h vendored Normal file
View file

@ -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_

162
netwerk/cache/nsCacheMetaData.cpp vendored Normal file
View file

@ -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;
}

45
netwerk/cache/nsCacheMetaData.h vendored Normal file
View file

@ -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

158
netwerk/cache/nsCacheRequest.h vendored Normal file
View file

@ -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_

3209
netwerk/cache/nsCacheService.cpp vendored Normal file

File diff suppressed because it is too large Load diff

392
netwerk/cache/nsCacheService.h vendored Normal file
View file

@ -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_

147
netwerk/cache/nsCacheSession.cpp vendored Normal file
View file

@ -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;
}

67
netwerk/cache/nsCacheSession.h vendored Normal file
View file

@ -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_

89
netwerk/cache/nsCacheUtils.cpp vendored Normal file
View file

@ -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;
}

43
netwerk/cache/nsCacheUtils.h vendored Normal file
View file

@ -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

454
netwerk/cache/nsDeleteDir.cpp vendored Normal file
View file

@ -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;
}

80
netwerk/cache/nsDeleteDir.h vendored Normal file
View file

@ -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__

72
netwerk/cache/nsDiskCache.h vendored Normal file
View file

@ -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_

371
netwerk/cache/nsDiskCacheBinding.cpp vendored Normal file
View file

@ -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;
}

123
netwerk/cache/nsDiskCacheBinding.h vendored Normal file
View file

@ -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_ */

404
netwerk/cache/nsDiskCacheBlockFile.cpp vendored Normal file
View file

@ -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);
}

69
netwerk/cache/nsDiskCacheBlockFile.h vendored Normal file
View file

@ -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_

1149
netwerk/cache/nsDiskCacheDevice.cpp vendored Normal file

File diff suppressed because it is too large Load diff

116
netwerk/cache/nsDiskCacheDevice.h vendored Normal file
View file

@ -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_

2906
netwerk/cache/nsDiskCacheDeviceSQL.cpp vendored Normal file

File diff suppressed because it is too large Load diff

290
netwerk/cache/nsDiskCacheDeviceSQL.h vendored Normal file
View file

@ -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__

133
netwerk/cache/nsDiskCacheEntry.cpp vendored Normal file
View file

@ -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;
}

100
netwerk/cache/nsDiskCacheEntry.h vendored Normal file
View file

@ -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_ */

1430
netwerk/cache/nsDiskCacheMap.cpp vendored Normal file

File diff suppressed because it is too large Load diff

577
netwerk/cache/nsDiskCacheMap.h vendored Normal file
View file

@ -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_

693
netwerk/cache/nsDiskCacheStreams.cpp vendored Normal file
View file

@ -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;
}

70
netwerk/cache/nsDiskCacheStreams.h vendored Normal file
View file

@ -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_

142
netwerk/cache/nsICache.idl vendored Normal file
View file

@ -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;
};

View file

@ -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);
};

32
netwerk/cache/nsICacheListener.idl vendored Normal file
View file

@ -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);
};

98
netwerk/cache/nsICacheService.idl vendored Normal file
View file

@ -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;
};

88
netwerk/cache/nsICacheSession.idl vendored Normal file
View file

@ -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;
};

123
netwerk/cache/nsICacheVisitor.idl vendored Normal file
View file

@ -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();
};

617
netwerk/cache/nsMemoryCacheDevice.cpp vendored Normal file
View file

@ -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;
}

124
netwerk/cache/nsMemoryCacheDevice.h vendored Normal file
View file

@ -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_