Bug 1627075 - Allow lazily initializing nsZipArchives r=froydnj

Opening our Omnijars can be expensive, and it should be deferrable until after
startup is completed, provided we have a startup cache. In a previous patch in this
stack, we implemented caching of the zip central directory for omnijars, but we
still have to open the file in order to hand the object off to various omnijar
consumers. In a later patch, we will wrap nsZipArchive access in a class which
will allow us to transparently cache nsZipArchive results. These two get us
most of the way to not needing to read from the underlying omnijar files during
startup, but there are still nontrivial pieces, like nsZipFind for instance,
which we don't want to just duplicate inside of a wrapper class, so we would
like to sort out a way in which we can use an nsZipArchive class, but not
actually back it up with the real underlying file until we really need data
from it which we can't find in a cache.

Depends on D77633

Differential Revision: https://phabricator.services.mozilla.com/D78584
This commit is contained in:
Doug Thayer 2020-07-07 17:02:42 +00:00
Родитель fafe31f134
Коммит 2046625fac
3 изменённых файлов: 249 добавлений и 36 удалений

Просмотреть файл

@ -342,17 +342,19 @@ nsZipHandle::~nsZipHandle() {
// nsZipArchive::OpenArchive
//---------------------------------------------
nsresult nsZipArchive::OpenArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd,
const uint8_t* aCachedCentral,
size_t aCachedCentralSize) {
Span<const uint8_t> aCachedCentral) {
mFd = aZipHandle;
//-- get table of contents for archive
nsresult rv;
if (aCachedCentral) {
rv = BuildFileListFromBuffer(aCachedCentral,
aCachedCentral + aCachedCentralSize);
} else {
rv = BuildFileList(aFd);
nsresult rv = NS_OK;
if (!mBuiltFileList) {
if (!aCachedCentral.IsEmpty()) {
auto* start = aCachedCentral.Elements();
auto* end = start + aCachedCentral.Length();
rv = BuildFileListFromBuffer(start, end);
} else {
rv = BuildFileList(aFd);
}
}
if (NS_SUCCEEDED(rv)) {
if (aZipHandle->mFile && XRE_IsParentProcess()) {
@ -408,8 +410,7 @@ nsresult nsZipArchive::OpenArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd,
}
nsresult nsZipArchive::OpenArchive(nsIFile* aFile,
const uint8_t* aCachedCentral,
size_t aCachedCentralSize) {
Span<const uint8_t> aCachedCentral) {
RefPtr<nsZipHandle> handle;
#if defined(XP_WIN)
mozilla::AutoFDClose fd;
@ -420,9 +421,9 @@ nsresult nsZipArchive::OpenArchive(nsIFile* aFile,
if (NS_FAILED(rv)) return rv;
#if defined(XP_WIN)
return OpenArchive(handle, fd.get(), aCachedCentral, aCachedCentralSize);
return OpenArchive(handle, fd.get(), aCachedCentral);
#else
return OpenArchive(handle, nullptr, aCachedCentral, aCachedCentralSize);
return OpenArchive(handle, nullptr, aCachedCentral);
#endif
}
@ -472,13 +473,75 @@ nsresult nsZipArchive::CloseArchive() {
// Let us also cleanup the mFiles table for re-use on the next 'open' call
memset(mFiles, 0, sizeof(mFiles));
mBuiltSynthetics = false;
AutoWriteLock lock(mLazyOpenLock);
mLazyOpenParams = Nothing();
return NS_OK;
}
nsresult nsZipArchive::EnsureArchiveOpenedOnDisk() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
return NS_OK;
}
}
AutoWriteLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread beat us to opening the archive while we were waiting on
// the mutex.
return NS_OK;
}
nsresult rv = OpenArchive(mLazyOpenParams->mFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
mLazyOpenParams = Nothing();
return NS_OK;
}
nsresult nsZipArchive::EnsureFileListBuilt() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams || mBuiltFileList) {
return NS_OK;
}
}
AutoWriteLock lock(mLazyOpenLock);
if (!mLazyOpenParams || mBuiltFileList) {
// Another thread beat us to building the file list while we were waiting
// on the mutex.
return NS_OK;
}
nsresult rv;
if (!mLazyOpenParams->mCachedCentral.IsEmpty()) {
auto* start = mLazyOpenParams->mCachedCentral.Elements();
auto* end = start + mLazyOpenParams->mCachedCentral.Length();
rv = BuildFileListFromBuffer(start, end);
} else {
rv = OpenArchive(mLazyOpenParams->mFile);
mLazyOpenParams = Nothing();
}
return rv;
}
//---------------------------------------------
// nsZipArchive::GetItem
//---------------------------------------------
nsZipItem* nsZipArchive::GetItem(const char* aEntryName) {
nsresult rv = EnsureFileListBuilt();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
if (aEntryName) {
uint32_t len = strlen(aEntryName);
//-- If the request is for a directory, make sure that synthetic entries
@ -562,6 +625,12 @@ nsresult nsZipArchive::ExtractFile(nsZipItem* item, nsIFile* outFile,
nsresult nsZipArchive::FindInit(const char* aPattern, nsZipFind** aFind) {
if (!aFind) return NS_ERROR_ILLEGAL_VALUE;
nsresult rv = EnsureFileListBuilt();
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
// null out param in case an error happens
*aFind = nullptr;
@ -569,7 +638,7 @@ nsresult nsZipArchive::FindInit(const char* aPattern, nsZipFind** aFind) {
char* pattern = 0;
// Create synthetic directory entries on demand
nsresult rv = BuildSynthetics();
rv = BuildSynthetics();
if (rv != NS_OK) return rv;
// validate the pattern
@ -613,7 +682,12 @@ nsresult nsZipFind::FindNext(const char** aResult, uint16_t* aNameLen) {
*aResult = 0;
*aNameLen = 0;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->GetFD())
// NOTE: don't use GetFD here. if mFd is not null, then we need to have this
// fault handler, as we may be reading from the memory mapped file. However
// if it is null, then we can guarantee that we're reading here from a cached
// buffer, which we assume is not mapped to a file.
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->mFd)
// we start from last match, look for next
while (mSlot < ZIP_TABSIZE) {
// move to next in current chain, or move to new slot
@ -659,6 +733,7 @@ nsZipItem* nsZipArchive::CreateZipItem() {
// nsZipArchive::BuildFileList
//---------------------------------------------
nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd) {
mBuiltFileList = true;
// Get archive size using end pos
const uint8_t* buf;
const uint8_t* startp = mFd->mFileData;
@ -689,7 +764,7 @@ nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd) {
return NS_ERROR_FILE_CORRUPTED;
}
uintptr_t startpInt = (uintptr_t)startp;
uintptr_t startpInt = reinterpret_cast<uintptr_t>(startp);
if (startpInt + centralOffset < startpInt || centralOffset > mFd->mLen) {
return NS_ERROR_FILE_CORRUPTED;
}
@ -737,6 +812,7 @@ UniquePtr<uint8_t[]> nsZipArchive::CopyCentralDirectoryBuffer(size_t* aSize) {
nsresult nsZipArchive::BuildFileListFromBuffer(const uint8_t* aBuf,
const uint8_t* aEnd) {
mBuiltFileList = true;
const uint8_t* buf = aBuf;
//-- Read the central directory headers
uint32_t sig = 0;
@ -870,15 +946,64 @@ nsresult nsZipArchive::BuildSynthetics() {
}
nsZipHandle* nsZipArchive::GetFD() {
if (!mFd) return nullptr;
nsresult rv = EnsureArchiveOpenedOnDisk();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return mFd.get();
}
void nsZipArchive::GetURIString(nsACString& result) {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
mFd->mFile.GetURIString(result);
return;
}
}
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread consumed mLazyOpenParams while we were waiting.
mFd->mFile.GetURIString(result);
return;
}
// This is a bit tricky - typically, we could just
// NS_GetURLSpecFromActualFile from mLazyOpenParams->mFile or from
// mFd->mFile.GetBaseFile(), depending on which we currently have. However,
// this won't actually be correct if this zip archive is nested inside
// another archive. However, at present, we know that mLazyOpenParams can
// only be here if we were opened from a real underlying file, so we assume
// that we're safe to do this. Any future code that breaks this assumption
// will need to update things here.
NS_GetURLSpecFromActualFile(mLazyOpenParams->mFile, result);
}
already_AddRefed<nsIFile> nsZipArchive::GetBaseFile() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
return mFd->mFile.GetBaseFile();
}
}
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread consumed mLazyOpenParams while we were waiting.
return mFd->mFile.GetBaseFile();
}
nsCOMPtr<nsIFile> file = mLazyOpenParams->mFile;
return file.forget();
}
//---------------------------------------------
// nsZipArchive::GetDataOffset
//---------------------------------------------
uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
nsresult rv = EnsureArchiveOpenedOnDisk();
MOZ_RELEASE_ASSERT(!NS_FAILED(rv),
"Should have been able to open the zip archive");
uint32_t offset;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
@ -909,6 +1034,10 @@ uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
//---------------------------------------------
const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
nsresult rv = EnsureArchiveOpenedOnDisk();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
uint32_t offset = GetDataOffset(aItem);
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
@ -947,6 +1076,8 @@ nsZipArchive::nsZipArchive()
mZipCentralSize(0),
mCommentLen(0),
mBuiltSynthetics(false),
mBuiltFileList(false),
mLazyOpenLock("nsZipArchive::mLazyOpenLock"),
mUseZipLog(false) {
// initialize the table to nullptr
memset(mFiles, 0, sizeof(mFiles));
@ -1254,12 +1385,14 @@ uint8_t* nsZipCursor::ReadOrCopy(uint32_t* aBytesRead, bool aCopy) {
nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive* aZip, const char* aEntryName,
bool doCRC)
: mReturnBuf(nullptr), mReadlen(0) {
nsZipItem* item = aZip->GetItem(aEntryName);
if (!item) {
return;
}
// make sure the ziparchive hangs around
mZipHandle = aZip->GetFD();
nsZipItem* item = aZip->GetItem(aEntryName);
if (!item) return;
uint32_t size = 0;
bool compressed = (item->Compression() == DEFLATED);
#ifdef MOZ_JAR_BROTLI

Просмотреть файл

@ -17,9 +17,11 @@
#include "nsIFile.h"
#include "nsISupportsImpl.h" // For mozilla::ThreadSafeAutoRefCnt
#include "mozilla/ArenaAllocator.h"
#include "mozilla/Atomics.h"
#include "mozilla/FileUtils.h"
#include "mozilla/FileLocation.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/RWLock.h"
class nsZipFind;
struct PRFileDesc;
@ -84,6 +86,22 @@ class nsZipArchive final {
/** destructing the object closes the archive */
~nsZipArchive();
/**
* LazyOpenArchiveParams is a class which is used to store cached
* contents of omnijars.
*
*/
struct LazyOpenArchiveParams {
nsCOMPtr<nsIFile> mFile;
mozilla::Span<const uint8_t> mCachedCentral;
LazyOpenArchiveParams(nsIFile* aFile,
mozilla::Span<const uint8_t> aCachedCentral)
: mFile(nullptr), mCachedCentral(aCachedCentral) {
aFile->Clone(getter_AddRefs(mFile));
}
};
public:
static const char* sFileCorruptedReason;
@ -102,12 +120,11 @@ class nsZipArchive final {
optimization
* @param aCachedCentral Optional cached buffer containing the zip central
for this zip.
* @param aCachedCentralSize Optional size of aCachedCentral.
* @return status code
*/
nsresult OpenArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd = nullptr,
const uint8_t* aCachedCentral = nullptr,
size_t aCachedCentralSize = 0);
mozilla::Span<const uint8_t> aCachedCentral =
mozilla::Span<const uint8_t>());
/**
* OpenArchive
@ -117,11 +134,44 @@ class nsZipArchive final {
* @param aFile The file used to access the zip
* @param aCachedCentral Optional cached buffer containing the zip central
for this zip.
* @param aCachedCentralSize Optional size of aCachedCentral.
* @return status code
*/
nsresult OpenArchive(nsIFile* aFile, const uint8_t* aCachedCentral = nullptr,
size_t aCachedCentralSize = 0);
nsresult OpenArchive(nsIFile* aFile,
mozilla::Span<const uint8_t> aCachedCentral =
mozilla::Span<const uint8_t>());
/**
* Ensures underlying archive is opened, if it was opened with
* LazyOpenArchive.
*
* Convenience function that generates nsZipHandle
*
* @param aFile The file used to access the zip
* @return status code
*/
nsresult EnsureArchiveOpenedOnDisk();
/**
* OpenArchive
*
* Lazily opens the zip archive on the first request to get data from it.
* NOTE: The buffer provided for aCachedCentral must outlive this
* nsZipArchive. This is presently true for the StartupCache, as it ensures
* that even past cache invalidation, all accessed buffers persist for the
* lifetime of the application, but we will need to ensure that this remains
* true.
*
* @param aFile The file used to access the zip
* @param aCachedCentral Cached buffer containing the zip central
for this zip.
* @return status code
*/
nsresult LazyOpenArchive(nsIFile* aFile,
mozilla::Span<const uint8_t> aCachedCentral) {
mozilla::AutoWriteLock lock(mLazyOpenLock);
mLazyOpenParams.emplace(aFile, aCachedCentral);
return NS_OK;
}
/**
* Test the integrity of items in this archive by running
@ -177,6 +227,21 @@ class nsZipArchive final {
*/
nsZipHandle* GetFD();
/*
* Gets the URI string to the mapped file. One could get this URI string
* in a roundabout way using GetFD, but GetFD requires opening the file for
* read access, which can be expensive.
*/
void GetURIString(nsACString& result);
/*
* Gets the underlying nsIFile pointer. Like GetURIString, this is to be
* preferred over GetFD where possible, because it does not require opening
* the file for read access, which can be expensive, and is to be avoided
* when possible during application startup.
*/
already_AddRefed<nsIFile> GetBaseFile();
/**
* Gets the data offset.
* @param aItem Pointer to nsZipItem
@ -231,10 +296,14 @@ class nsZipArchive final {
// Whether we synthesized the directory entries
bool mBuiltSynthetics;
bool mBuiltFileList;
// file handle
RefPtr<nsZipHandle> mFd;
mozilla::Maybe<LazyOpenArchiveParams> mLazyOpenParams;
mozilla::RWLock mLazyOpenLock;
// file URI, for logging
nsCString mURI;
@ -248,6 +317,7 @@ class nsZipArchive final {
nsresult BuildFileList(PRFileDesc* aFd = nullptr);
nsresult BuildFileListFromBuffer(const uint8_t* aBuf, const uint8_t* aEnd);
nsresult BuildSynthetics();
nsresult EnsureFileListBuilt();
nsZipArchive& operator=(const nsZipArchive& rhs) = delete;
nsZipArchive(const nsZipArchive& rhs) = delete;

Просмотреть файл

@ -93,20 +93,30 @@ void Omnijar::InitOne(nsIFile* aPath, Type aType) {
centralBufLength = 0;
}
}
if (NS_FAILED(zipReader->OpenArchive(file, centralBuf, centralBufLength))) {
if (NS_FAILED(zipReader->OpenArchive(
file, MakeSpan(centralBuf, centralBufLength)))) {
return;
}
if (cache && !centralBuf) {
size_t bufSize;
// Annoyingly, nsZipArchive and the startupcache use different types to
// represent bytes (uint8_t vs char), so we have to do a little dance to
// convert the UniquePtr over.
UniquePtr<char[]> centralBuf(reinterpret_cast<char*>(
zipReader->CopyCentralDirectoryBuffer(&bufSize).release()));
if (centralBuf) {
cache->PutBuffer(startupCacheKey.get(), std::move(centralBuf), bufSize);
if (!centralBuf) {
if (NS_FAILED(zipReader->OpenArchive(file))) {
return;
}
if (cache) {
size_t bufSize;
// Annoyingly, nsZipArchive and the startupcache use different types to
// represent bytes (uint8_t vs char), so we have to do a little dance to
// convert the UniquePtr over.
UniquePtr<char[]> centralBuf(reinterpret_cast<char*>(
zipReader->CopyCentralDirectoryBuffer(&bufSize).release()));
if (centralBuf) {
cache->PutBuffer(startupCacheKey.get(), std::move(centralBuf), bufSize);
}
}
} else {
if (NS_FAILED(zipReader->LazyOpenArchive(
file, MakeSpan(centralBuf, centralBufLength)))) {
return;
}
}