/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 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 <string.h>
#include "nsJARInputStream.h"
#include "nsJAR.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Logging.h"
#include "mozilla/Omnijar.h"
#include "mozilla/Unused.h"
#ifdef XP_UNIX
# include <sys/stat.h>
#elif defined(XP_WIN)
# include <io.h>
#endif
using namespace mozilla;
static LazyLogModule gJarLog(
"nsJAR");
#ifdef LOG
# undef LOG
#endif
#ifdef LOG_ENABLED
# undef LOG_ENABLED
#endif
#define LOG(args) MOZ_LOG(gJarLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() MOZ_LOG_TEST(gJarLog, mozilla::LogLevel::Debug)
//----------------------------------------------
// nsJAR constructor/destructor
//----------------------------------------------
// The following initialization makes a guess of 10 entries per jarfile.
nsJAR::nsJAR()
: mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
mLock(
"nsJAR::mLock"),
mCache(nullptr) {}
nsJAR::~nsJAR() { Close(); }
NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
NS_IMPL_ADDREF(nsJAR)
// Custom Release method works with nsZipReaderCache...
// Release might be called from multi-thread, we have to
// take this function carefully to avoid delete-after-use.
MozExternalRefCountType nsJAR::Release(
void) {
nsrefcnt count;
MOZ_ASSERT(0 != mRefCnt,
"dup release");
RefPtr<nsZipReaderCache> cache;
if (mRefCnt == 2) {
// don't use a lock too frequently
// Use a mutex here to guarantee mCache is not racing and the target
// instance is still valid to increase ref-count.
RecursiveMutexAutoLock lock(mLock);
cache = mCache;
mCache = nullptr;
}
if (cache) {
DebugOnly<nsresult> rv = cache->ReleaseZip(
this);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"failed to release zip file");
}
count = --mRefCnt;
// don't access any member variable after this line
NS_LOG_RELEASE(
this, count,
"nsJAR");
if (0 == count) {
mRefCnt = 1;
/* stabilize */
/* enable this to find non-threadsafe destructors: */
/* NS_ASSERT_OWNINGTHREAD(nsJAR); */
delete this;
return 0;
}
return count;
}
//----------------------------------------------
// nsIZipReader implementation
//----------------------------------------------
NS_IMETHODIMP
nsJAR::Open(nsIFile* zipFile) {
NS_ENSURE_ARG_POINTER(zipFile);
RecursiveMutexAutoLock lock(mLock);
LOG((
"Open[%p] %s",
this, zipFile->HumanReadablePath().get()));
if (mZip)
return NS_ERROR_FAILURE;
// Already open!
mZipFile = zipFile;
mOuterZipEntry.Truncate();
// The omnijar is special, it is opened early on and closed late
RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
if (!zip) {
zip = nsZipArchive::OpenArchive(zipFile);
}
mZip = zip;
return mZip ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsJAR::OpenInner(nsIZipReader* aZipReader,
const nsACString& aZipEntry) {
nsresult rv;
LOG((
"OpenInner[%p] %s",
this, PromiseFlatCString(aZipEntry).get()));
NS_ENSURE_ARG_POINTER(aZipReader);
nsCOMPtr<nsIFile> zipFile;
rv = aZipReader->GetFile(getter_AddRefs(zipFile));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsZipArchive> innerZip =
mozilla::Omnijar::GetInnerReader(zipFile, aZipEntry);
if (innerZip) {
RecursiveMutexAutoLock lock(mLock);
if (mZip) {
return NS_ERROR_FAILURE;
}
mZip = innerZip;
return NS_OK;
}
bool exist;
rv = aZipReader->HasEntry(aZipEntry, &exist);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
RefPtr<nsZipHandle> handle;
{
nsJAR* outerJAR =
static_cast<nsJAR*>(aZipReader);
RecursiveMutexAutoLock outerLock(outerJAR->mLock);
rv = nsZipHandle::Init(outerJAR->mZip.get(), aZipEntry,
getter_AddRefs(handle));
NS_ENSURE_SUCCESS(rv, rv);
}
RecursiveMutexAutoLock lock(mLock);
MOZ_ASSERT(!mZip,
"Another thread tried to open this nsJAR racily!");
mZipFile = zipFile.forget();
mOuterZipEntry.Assign(aZipEntry);
mZip = nsZipArchive::OpenArchive(handle);
return mZip ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsJAR::OpenMemory(
void* aData, uint32_t aLength) {
NS_ENSURE_ARG_POINTER(aData);
RecursiveMutexAutoLock lock(mLock);
if (mZip)
return NS_ERROR_FAILURE;
// Already open!
RefPtr<nsZipHandle> handle;
nsresult rv = nsZipHandle::Init(
static_cast<uint8_t*>(aData), aLength,
getter_AddRefs(handle));
if (NS_FAILED(rv))
return rv;
mZip = nsZipArchive::OpenArchive(handle);
return mZip ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsJAR::GetFile(nsIFile** result) {
RecursiveMutexAutoLock lock(mLock);
LOG((
"GetFile[%p]",
this));
*result = mZipFile;
NS_IF_ADDREF(*result);
return NS_OK;
}
NS_IMETHODIMP
nsJAR::Close() {
RecursiveMutexAutoLock lock(mLock);
LOG((
"Close[%p]",
this));
if (!mZip) {
return NS_ERROR_FAILURE;
// Never opened or already closed.
}
mZip = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsJAR::Test(
const nsACString& aEntryName) {
RecursiveMutexAutoLock lock(mLock);
if (!mZip) {
return NS_ERROR_FAILURE;
}
return mZip->Test(aEntryName);
}
NS_IMETHODIMP
nsJAR::Extract(
const nsACString& aEntryName, nsIFile* outFile) {
// nsZipArchive and zlib are not thread safe
// we need to use a lock to prevent bug #51267
RecursiveMutexAutoLock lock(mLock);
if (!mZip) {
return NS_ERROR_FAILURE;
}
LOG((
"Extract[%p] %s",
this, PromiseFlatCString(aEntryName).get()));
nsZipItem* item = mZip->GetItem(aEntryName);
NS_ENSURE_TRUE(item, NS_ERROR_FILE_NOT_FOUND);
// Remove existing file or directory so we set permissions correctly.
// If it's a directory that already exists and contains files, throw
// an exception and return.
nsresult rv = outFile->Remove(
false);
if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || rv == NS_ERROR_FAILURE)
return rv;
if (item->IsDirectory()) {
rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
// XXX Do this in nsZipArchive? It would be nice to keep extraction
// XXX code completely there, but that would require a way to get a
// XXX PRDir from outFile.
}
else {
PRFileDesc* fd;
rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(),
&fd);
if (NS_FAILED(rv))
return rv;
// ExtractFile also closes the fd handle and resolves the symlink if needed
rv = mZip->ExtractFile(item, outFile, fd);
}
if (NS_FAILED(rv))
return rv;
// nsIFile needs milliseconds, while prtime is in microseconds.
// non-fatal if this fails, ignore errors
outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
return NS_OK;
}
NS_IMETHODIMP
nsJAR::GetEntry(
const nsACString& aEntryName, nsIZipEntry** result) {
RecursiveMutexAutoLock lock(mLock);
LOG((
"GetEntry[%p] %s",
this, PromiseFlatCString(aEntryName).get()));
if (!mZip) {
return NS_ERROR_FAILURE;
}
nsZipItem* zipItem = mZip->GetItem(aEntryName);
NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_NOT_FOUND);
RefPtr<nsJARItem> jarItem =
new nsJARItem(zipItem);
*result = jarItem.forget().take();
return NS_OK;
}
NS_IMETHODIMP
nsJAR::HasEntry(
const nsACString& aEntryName,
bool* result) {
RecursiveMutexAutoLock lock(mLock);
LOG((
"HasEntry[%p] %s",
this, PromiseFlatCString(aEntryName).get()));
if (!mZip) {
return NS_ERROR_FAILURE;
}
*result = mZip->GetItem(aEntryName) != nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsJAR::FindEntries(
const nsACString& aPattern,
nsIUTF8StringEnumerator** result) {
NS_ENSURE_ARG_POINTER(result);
RecursiveMutexAutoLock lock(mLock);
LOG((
"FindEntries[%p] %s",
this, PromiseFlatCString(aPattern).get()));
if (!mZip) {
return NS_ERROR_FAILURE;
}
nsZipFind* find;
nsresult rv = mZip->FindInit(
aPattern.IsEmpty() ? nullptr : PromiseFlatCString(aPattern).get(), &find);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsIUTF8StringEnumerator> zipEnum =
new nsJAREnumerator(find);
// Callers use getter_addrefs
*result = zipEnum.forget().take();
return NS_OK;
}
NS_IMETHODIMP
nsJAR::GetInputStream(
const nsACString& aEntryName, nsIInputStream** result) {
NS_ENSURE_ARG_POINTER(result);
RecursiveMutexAutoLock lock(mLock);
if (!mZip) {
return NS_ERROR_FAILURE;
}
LOG((
"GetInputStream[%p] %s",
this, PromiseFlatCString(aEntryName).get()));
// Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
nsZipItem* item = nullptr;
const nsCString& entry = PromiseFlatCString(aEntryName);
if (*entry.get()) {
// First check if item exists in jar
item = mZip->GetItem(entry);
if (!item)
return NS_ERROR_FILE_NOT_FOUND;
}
RefPtr<nsJARInputStream> jis =
new nsJARInputStream();
nsresult rv = NS_OK;
if (!item || item->IsDirectory()) {
rv = jis->InitDirectory(
this, entry.get());
}
else {
RefPtr<nsZipHandle> fd = mZip->GetFD();
rv = jis->InitFile(fd, mZip->GetData(item), item);
}
if (NS_SUCCEEDED(rv)) {
// Callers use getter_addrefs
*result = jis.forget().take();
}
return rv;
}
nsresult nsJAR::GetFullJarPath(nsACString& aResult) {
RecursiveMutexAutoLock lock(mLock);
NS_ENSURE_ARG_POINTER(mZipFile);
nsresult rv = mZipFile->GetPersistentDescriptor(aResult);
if (NS_FAILED(rv)) {
return rv;
}
if (mOuterZipEntry.IsEmpty()) {
aResult.InsertLiteral(
"file:", 0);
}
else {
aResult.InsertLiteral(
"jar:", 0);
aResult.AppendLiteral(
"!/");
aResult.Append(mOuterZipEntry);
}
return NS_OK;
}
nsresult nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) {
RecursiveMutexAutoLock lock(mLock);
if (!aNSPRFileDesc) {
return NS_ERROR_ILLEGAL_VALUE;
}
*aNSPRFileDesc = nullptr;
if (!mZip) {
return NS_ERROR_FAILURE;
}
RefPtr<nsZipHandle> handle = mZip->GetFD();
if (!handle) {
return NS_ERROR_FAILURE;
}
return handle->GetNSPRFileDesc(aNSPRFileDesc);
}
//----------------------------------------------
// nsJAR private implementation
//----------------------------------------------
nsresult nsJAR::LoadEntry(
const nsACString& aFilename, nsCString& aBuf) {
//-- Get a stream for reading the file
nsresult rv;
nsCOMPtr<nsIInputStream> manifestStream;
rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
if (NS_FAILED(rv))
return NS_ERROR_FILE_NOT_FOUND;
//-- Read the manifest file into memory
char* buf;
uint64_t len64;
rv = manifestStream->Available(&len64);
if (NS_FAILED(rv))
return rv;
NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED);
// bug 164695
uint32_t len = (uint32_t)len64;
buf = (
char*)malloc(len + 1);
if (!buf)
return NS_ERROR_OUT_OF_MEMORY;
uint32_t bytesRead;
rv = manifestStream->Read(buf, len, &bytesRead);
if (bytesRead != len) {
rv = NS_ERROR_FILE_CORRUPTED;
}
if (NS_FAILED(rv)) {
free(buf);
return rv;
}
buf[len] =
'\0';
// Null-terminate the buffer
aBuf.Adopt(buf, len);
return NS_OK;
}
int32_t nsJAR::ReadLine(
const char** src) {
if (!*src) {
return 0;
}
//--Moves pointer to beginning of next line and returns line length
// not including CR/LF.
int32_t length;
const char* eol = strpbrk(*src,
"\r\n");
if (eol == nullptr)
// Probably reached end of file before newline
{
length = strlen(*src);
if (length == 0)
// immediate end-of-file
*src = nullptr;
else // some data left on this line
*src += length;
}
else {
length = eol - *src;
if (eol[0] ==
'\r' && eol[1] ==
'\n')
// CR LF, so skip 2
*src = eol + 2;
else // Either CR or LF, so skip 1
*src = eol + 1;
}
return length;
}
NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator, nsIStringEnumerator)
//----------------------------------------------
// nsJAREnumerator::HasMore
//----------------------------------------------
NS_IMETHODIMP
nsJAREnumerator::HasMore(
bool* aResult) {
// try to get the next element
if (!mName) {
NS_ASSERTION(mFind,
"nsJAREnumerator: Missing zipFind.");
nsresult rv = mFind->FindNext(&mName, &mNameLen);
if (rv == NS_ERROR_FILE_NOT_FOUND) {
*aResult =
false;
// No more matches available
return NS_OK;
}
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
// no error translation
}
*aResult =
true;
return NS_OK;
}
//----------------------------------------------
// nsJAREnumerator::GetNext
//----------------------------------------------
NS_IMETHODIMP
nsJAREnumerator::GetNext(nsACString& aResult) {
// check if the current item is "stale"
if (!mName) {
bool bMore;
nsresult rv = HasMore(&bMore);
if (NS_FAILED(rv) || !bMore)
return NS_ERROR_FAILURE;
// no error translation
}
aResult.Assign(mName, mNameLen);
mName = 0;
// we just gave this one away
return NS_OK;
}
NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
nsJARItem::nsJARItem(nsZipItem* aZipItem)
: mSize(aZipItem->Size()),
mRealsize(aZipItem->RealSize()),
mCrc32(aZipItem->CRC32()),
mLastModTime(aZipItem->LastModTime()),
mCompression(aZipItem->Compression()),
mPermissions(aZipItem->Mode()),
mIsDirectory(aZipItem->IsDirectory()),
mIsSynthetic(aZipItem->isSynthetic) {}
//------------------------------------------
// nsJARItem::GetCompression
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetCompression(uint16_t* aCompression) {
NS_ENSURE_ARG_POINTER(aCompression);
*aCompression = mCompression;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetSize
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetSize(uint32_t* aSize) {
NS_ENSURE_ARG_POINTER(aSize);
*aSize = mSize;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetRealSize
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetRealSize(uint32_t* aRealsize) {
NS_ENSURE_ARG_POINTER(aRealsize);
*aRealsize = mRealsize;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetCrc32
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetCRC32(uint32_t* aCrc32) {
NS_ENSURE_ARG_POINTER(aCrc32);
*aCrc32 = mCrc32;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetIsDirectory
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetIsDirectory(
bool* aIsDirectory) {
NS_ENSURE_ARG_POINTER(aIsDirectory);
*aIsDirectory = mIsDirectory;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetIsSynthetic
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetIsSynthetic(
bool* aIsSynthetic) {
NS_ENSURE_ARG_POINTER(aIsSynthetic);
*aIsSynthetic = mIsSynthetic;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetLastModifiedTime
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) {
NS_ENSURE_ARG_POINTER(aLastModTime);
*aLastModTime = mLastModTime;
return NS_OK;
}
//------------------------------------------
// nsJARItem::GetPermissions
//------------------------------------------
NS_IMETHODIMP
nsJARItem::GetPermissions(uint32_t* aPermissions) {
NS_ENSURE_ARG_POINTER(aPermissions);
*aPermissions = mPermissions;
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIZipReaderCache
NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver,
nsISupportsWeakReference)
nsZipReaderCache::nsZipReaderCache()
: mLock(
"nsZipReaderCache.mLock"),
mCacheSize(0),
mZips()
#ifdef ZIP_CACHE_HIT_RATE
,
mZipCacheLookups(0),
mZipCacheHits(0),
mZipCacheFlushes(0),
mZipSyncMisses(0)
#endif
{
}
NS_IMETHODIMP
nsZipReaderCache::Init(uint32_t cacheSize) {
MutexAutoLock lock(mLock);
mCacheSize = cacheSize;
// Register as a memory pressure observer
nsCOMPtr<nsIObserverService> os =
do_GetService(
"@mozilla.org/observer-service;1");
if (os) {
os->AddObserver(
this,
"memory-pressure",
true);
os->AddObserver(
this,
"chrome-flush-caches",
true);
os->AddObserver(
this,
"flush-cache-entry",
true);
}
// ignore failure of the observer registration.
return NS_OK;
}
nsZipReaderCache::~nsZipReaderCache() {
for (
const auto& zip : mZips.Values()) {
zip->SetZipReaderCache(nullptr);
}
#ifdef ZIP_CACHE_HIT_RATE
printf(
"nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed "
"%d\n",
mCacheSize, mZipCacheHits, mZipCacheLookups,
(
float)mZipCacheHits / mZipCacheLookups, mZipCacheFlushes,
mZipSyncMisses);
#endif
}
NS_IMETHODIMP
nsZipReaderCache::IsCached(nsIFile* zipFile,
bool* aResult) {
NS_ENSURE_ARG_POINTER(zipFile);
nsresult rv;
MutexAutoLock lock(mLock);
nsAutoCString uri;
rv = zipFile->GetPersistentDescriptor(uri);
if (NS_FAILED(rv))
return rv;
uri.InsertLiteral(
"file:", 0);
*aResult = mZips.Contains(uri);
return NS_OK;
}
nsresult nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result,
bool failOnMiss) {
NS_ENSURE_ARG_POINTER(zipFile);
nsresult rv;
MutexAutoLock lock(mLock);
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheLookups++;
#endif
nsAutoCString uri;
rv = zipFile->GetPersistentDescriptor(uri);
if (NS_FAILED(rv))
return rv;
uri.InsertLiteral(
"file:", 0);
RefPtr<nsJAR> zip;
mZips.Get(uri, getter_AddRefs(zip));
if (zip) {
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheHits++;
#endif
zip->ClearReleaseTime();
}
else {
if (failOnMiss) {
return NS_ERROR_CACHE_KEY_NOT_FOUND;
}
zip =
new nsJAR();
zip->SetZipReaderCache(
this);
rv = zip->Open(zipFile);
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(!mZips.Contains(uri));
mZips.InsertOrUpdate(uri, RefPtr{zip});
}
zip.forget(result);
return rv;
}
NS_IMETHODIMP
nsZipReaderCache::GetZipIfCached(nsIFile* zipFile, nsIZipReader** result) {
return GetZip(zipFile, result,
true);
}
NS_IMETHODIMP
nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result) {
return GetZip(zipFile, result,
false);
}
NS_IMETHODIMP
nsZipReaderCache::GetInnerZip(nsIFile* zipFile,
const nsACString& entry,
nsIZipReader** result) {
NS_ENSURE_ARG_POINTER(zipFile);
nsCOMPtr<nsIZipReader> outerZipReader;
nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
NS_ENSURE_SUCCESS(rv, rv);
MutexAutoLock lock(mLock);
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheLookups++;
#endif
nsAutoCString uri;
rv = zipFile->GetPersistentDescriptor(uri);
if (NS_FAILED(rv))
return rv;
uri.InsertLiteral(
"jar:", 0);
uri.AppendLiteral(
"!/");
uri.Append(entry);
RefPtr<nsJAR> zip;
mZips.Get(uri, getter_AddRefs(zip));
if (zip) {
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheHits++;
#endif
zip->ClearReleaseTime();
}
else {
zip =
new nsJAR();
zip->SetZipReaderCache(
this);
rv = zip->OpenInner(outerZipReader, entry);
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(!mZips.Contains(uri));
mZips.InsertOrUpdate(uri, RefPtr{zip});
}
zip.forget(result);
return rv;
}
NS_IMETHODIMP
nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal) {
#if defined(XP_WIN)
MOZ_CRASH(
"Not implemented");
return NS_ERROR_NOT_IMPLEMENTED;
#else
if (!zipFile) {
return NS_ERROR_FAILURE;
}
nsresult rv;
nsAutoCString uri;
rv = zipFile->GetPersistentDescriptor(uri);
if (NS_FAILED(rv)) {
return rv;
}
uri.InsertLiteral(
"file:", 0);
MutexAutoLock lock(mLock);
RefPtr<nsJAR> zip;
mZips.Get(uri, getter_AddRefs(zip));
if (!zip) {
return NS_ERROR_FAILURE;
}
zip->ClearReleaseTime();
rv = zip->GetNSPRFileDesc(aRetVal);
// Do this to avoid possible deadlock on mLock with ReleaseZip().
{
MutexAutoUnlock unlock(mLock);
zip = nullptr;
}
return rv;
#endif /* XP_WIN */
}
nsresult nsZipReaderCache::ReleaseZip(nsJAR* zip) {
nsresult rv;
MutexAutoLock lock(mLock);
// It is possible that two thread compete for this zip. The dangerous
// case is where one thread Releases the zip and discovers that the ref
// count has gone to one. Before it can call this ReleaseZip method
// another thread calls our GetZip method. The ref count goes to two. That
// second thread then Releases the zip and the ref count goes to one. It
// then tries to enter this ReleaseZip method and blocks while the first
// thread is still here. The first thread continues and remove the zip from
// the cache and calls its Release method sending the ref count to 0 and
// deleting the zip. However, the second thread is still blocked at the
// start of ReleaseZip, but the 'zip' param now hold a reference to a
// deleted zip!
//
// So, we are going to try safeguarding here by searching our hashtable while
// locked here for the zip. We return fast if it is not found.
bool found =
false;
for (
const auto& current : mZips.Values()) {
if (zip == current) {
found =
true;
break;
}
}
if (!found) {
#ifdef ZIP_CACHE_HIT_RATE
mZipSyncMisses++;
#endif
return NS_OK;
}
zip->SetReleaseTime();
if (mZips.Count() <= mCacheSize)
return NS_OK;
// Find the oldest zip.
nsJAR* oldest = nullptr;
for (
const auto& current : mZips.Values()) {
PRIntervalTime currentReleaseTime = current->GetReleaseTime();
if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
if (oldest == nullptr || currentReleaseTime < oldest->GetReleaseTime()) {
oldest = current;
}
}
}
// Because of the craziness above it is possible that there is no zip that
// needs removing.
if (!oldest)
return NS_OK;
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheFlushes++;
#endif
// remove from hashtable
nsAutoCString uri;
rv = oldest->GetFullJarPath(uri);
if (NS_FAILED(rv)) {
return rv;
}
// Retrieving and removing the JAR should be done without an extra AddRef
// and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
// an extra time.
RefPtr<nsJAR> removed;
mZips.Remove(uri, getter_AddRefs(removed));
NS_ASSERTION(removed,
"botched");
NS_ASSERTION(oldest == removed,
"removed wrong entry");
if (removed) removed->SetZipReaderCache(nullptr);
return NS_OK;
}
NS_IMETHODIMP
nsZipReaderCache::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aSomeData) {
if (strcmp(aTopic,
"memory-pressure") == 0) {
MutexAutoLock lock(mLock);
for (
auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
RefPtr<nsJAR>& current = iter.Data();
if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
current->SetZipReaderCache(nullptr);
iter.Remove();
}
}
}
else if (strcmp(aTopic,
"chrome-flush-caches") == 0) {
MutexAutoLock lock(mLock);
for (
const auto& current : mZips.Values()) {
current->SetZipReaderCache(nullptr);
}
mZips.Clear();
}
else if (strcmp(aTopic,
"flush-cache-entry") == 0) {
nsCOMPtr<nsIFile> file;
if (aSubject) {
file = do_QueryInterface(aSubject);
}
else if (aSomeData) {
nsDependentString fileName(aSomeData);
Unused << NS_NewLocalFile(fileName, getter_AddRefs(file));
}
if (!file)
return NS_OK;
nsAutoCString uri;
if (NS_FAILED(file->GetPersistentDescriptor(uri)))
return NS_OK;
uri.InsertLiteral(
"file:", 0);
MutexAutoLock lock(mLock);
RefPtr<nsJAR> zip;
mZips.Get(uri, getter_AddRefs(zip));
if (!zip)
return NS_OK;
#ifdef ZIP_CACHE_HIT_RATE
mZipCacheFlushes++;
#endif
zip->SetZipReaderCache(nullptr);
mZips.Remove(uri);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////