Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "FileUtilsImpl.h"
#include "CacheCipherKeyManager.h"
#include "DBSchema.h"
#include "mozilla/dom/InternalResponse.h"
#include "mozilla/dom/quota/DecryptingInputStream.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/EncryptingOutputStream.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/Unused.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIFile.h"
#include "nsIUUIDGenerator.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "snappy/snappy.h"
namespace mozilla::dom::cache {
static_assert(SNAPPY_VERSION == 0x010200);
using mozilla::dom::quota::Client;
using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::GetDirEntryKind;
using mozilla::dom::quota::nsIFileKind;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::QuotaObject;
namespace {
// Const variable for generate padding size.
const int64_t kRoundUpNumber = 20480;
// At the moment, the encrypted stream block size is assumed to be unchangeable
// between encrypting and decrypting blobs. This assumptions holds as long as we
// only encrypt in private browsing mode, but when we support encryption for
// persistent storage, this needs to be changed.
constexpr uint32_t kEncryptedStreamBlockSize = 4096;
enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP };
Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(nsIFile& aBaseDir,
const nsID& aId,
BodyFileType aType);
int64_t RoundUp(int64_t aX, int64_t aY);
// The alogrithm for generating padding refers to the mitigation approach in
// First, generate a random number between 0 and 100kB.
// Next, round up the sum of random number and response size to the nearest
// 20kB.
// Finally, the virtual padding size will be the result minus the response size.
int64_t BodyGeneratePadding(int64_t aBodyFileSize, uint32_t aPaddingInfo);
nsresult DirectoryPaddingWrite(nsIFile& aBaseDir,
DirPaddingFile aPaddingFileType,
int64_t aPaddingSize);
const auto kMorgueDirectory = u"morgue"_ns;
bool IsFileNotFoundError(const nsresult aRv) {
return aRv == NS_ERROR_FILE_NOT_FOUND;
}
Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyGetCacheDir(nsIFile& aBaseDir,
const nsID& aId) {
QM_TRY_UNWRAP(auto cacheDir, CloneFileAndAppend(aBaseDir, kMorgueDirectory));
// Some file systems have poor performance when there are too many files
// in a single directory. Mitigate this issue by spreading the body
// files out into sub-directories. We use the last byte of the ID for
// the name of the sub-directory.
QM_TRY(MOZ_TO_RESULT(cacheDir->Append(IntToString(aId.m3[7]))));
// Callers call this function without checking if the directory already
// exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
// just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
// reports.
QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(cacheDir->Create(nsIFile::DIRECTORY_TYPE, 0755)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
// Fallback.
ErrToDefaultOk<>));
return WrapNotNullUnchecked(std::move(cacheDir));
}
} // namespace
nsresult BodyCreateDir(nsIFile& aBaseDir) {
QM_TRY_INSPECT(const auto& bodyDir,
CloneFileAndAppend(aBaseDir, kMorgueDirectory));
// Callers call this function without checking if the directory already
// exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
// just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
// reports.
QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(bodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
// Fallback.
ErrToDefaultOk<>));
return NS_OK;
}
nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata,
nsIFile& aBaseDir) {
QM_TRY_INSPECT(const auto& bodyDir,
CloneFileAndAppend(aBaseDir, kMorgueDirectory));
QM_TRY(MOZ_TO_RESULT(RemoveNsIFileRecursively(aDirectoryMetadata, *bodyDir)));
return NS_OK;
}
Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey,
nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) {
MOZ_DIAGNOSTIC_ASSERT(aClosure);
MOZ_DIAGNOSTIC_ASSERT(aCallback);
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_FINAL));
{
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, Exists));
QM_TRY(OkIf(!exists), Err(NS_ERROR_FILE_ALREADY_EXISTS));
}
QM_TRY_INSPECT(const auto& tmpFile,
BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_TMP));
QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream,
CreateFileOutputStream(aDirectoryMetadata.mPersistenceType,
aDirectoryMetadata, Client::DOMCACHE,
tmpFile.get()));
const auto privateBody = aDirectoryMetadata.mIsPrivate;
if (privateBody) {
MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey);
fileStream = MakeRefPtr<quota::EncryptingOutputStream<CipherStrategy>>(
std::move(fileStream), kEncryptedStreamBlockSize, *aMaybeCipherKey);
}
const auto compressed = MakeRefPtr<SnappyCompressOutputStream>(fileStream);
const nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
nsCOMPtr<nsISupports> copyContext;
QM_TRY(MOZ_TO_RESULT(
NS_AsyncCopy(&aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
compressed->BlockSize(), aCallback, aClosure, true,
true, // close streams
getter_AddRefs(copyContext))));
return std::move(copyContext);
}
void BodyCancelWrite(nsISupports& aCopyContext) {
QM_WARNONLY_TRY(
QM_TO_RESULT(NS_CancelAsyncCopy(&aCopyContext, NS_ERROR_ABORT)));
// TODO The partially written file must be cleaned up after the async copy
// makes its callback.
}
Result<int64_t, nsresult> BodyFinalizeWrite(nsIFile& aBaseDir,
const nsID& aId) {
QM_TRY_INSPECT(const auto& tmpFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP));
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
nsAutoString finalFileName;
QM_TRY(MOZ_TO_RESULT(finalFile->GetLeafName(finalFileName)));
// It's fine to not notify the QuotaManager that the path has been changed,
// because its path will be updated and its size will be recalculated when
// opening file next time.
QM_TRY(MOZ_TO_RESULT(tmpFile->RenameTo(nullptr, finalFileName)));
QM_TRY_INSPECT(const int64_t& fileSize,
MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, GetFileSize));
return fileSize;
}
Result<int64_t, nsresult> GetBodyDiskSize(nsIFile& aBaseDir, const nsID& aId) {
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
QM_TRY_INSPECT(const int64_t& fileSize,
MOZ_TO_RESULT_INVOKE_MEMBER(*finalFile, GetFileSize));
return fileSize;
}
Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId, Maybe<CipherKey> aMaybeCipherKey) {
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
QM_TRY_UNWRAP(nsCOMPtr<nsIInputStream> fileInputStream,
CreateFileInputStream(aDirectoryMetadata.mPersistenceType,
aDirectoryMetadata, Client::DOMCACHE,
finalFile.get()));
auto privateBody = aDirectoryMetadata.mIsPrivate;
if (privateBody) {
MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey);
fileInputStream = new quota::DecryptingInputStream<CipherStrategy>(
WrapNotNull(std::move(fileInputStream)), kEncryptedStreamBlockSize,
*aMaybeCipherKey);
}
return WrapMovingNotNull(std::move(fileInputStream));
}
nsresult BodyMaybeUpdatePaddingSize(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId, const uint32_t aPaddingInfo, int64_t* aPaddingSizeInOut) {
MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeInOut);
QM_TRY_INSPECT(const auto& bodyFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP));
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_DIAGNOSTIC_ASSERT(quotaManager);
int64_t fileSize = 0;
RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
aDirectoryMetadata.mPersistenceType, aDirectoryMetadata, Client::DOMCACHE,
bodyFile.get(), -1, &fileSize);
MOZ_DIAGNOSTIC_ASSERT(quotaObject);
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
if (!quotaObject) {
return NS_ERROR_UNEXPECTED;
}
if (*aPaddingSizeInOut == InternalResponse::UNKNOWN_PADDING_SIZE) {
*aPaddingSizeInOut = BodyGeneratePadding(fileSize, aPaddingInfo);
}
MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeInOut >= 0);
if (!quotaObject->IncreaseSize(*aPaddingSizeInOut)) {
return NS_ERROR_FILE_NO_DEVICE_SPACE;
}
return NS_OK;
}
nsresult BodyDeleteFiles(const CacheDirectoryMetadata& aDirectoryMetadata,
nsIFile& aBaseDir, const nsTArray<nsID>& aIdList) {
for (const auto id : aIdList) {
QM_TRY_INSPECT(const auto& bodyDir, BodyGetCacheDir(aBaseDir, id));
const auto removeFileForId =
[&aDirectoryMetadata, &id](
nsIFile& bodyFile,
const nsACString& leafName) -> Result<bool, nsresult> {
nsID fileId{};
QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true,
([&aDirectoryMetadata, &bodyFile](const auto) {
DebugOnly<nsresult> result = RemoveNsIFile(
aDirectoryMetadata, bodyFile, /* aTrackQuota */ false);
MOZ_ASSERT(NS_SUCCEEDED(result));
}));
if (id.Equals(fileId)) {
DebugOnly<nsresult> result =
RemoveNsIFile(aDirectoryMetadata, bodyFile);
MOZ_ASSERT(NS_SUCCEEDED(result));
return true;
}
return false;
};
QM_TRY(MOZ_TO_RESULT(BodyTraverseFiles(aDirectoryMetadata, *bodyDir,
removeFileForId,
/* aCanRemoveFiles */ false,
/* aTrackQuota */ true)));
}
return NS_OK;
}
namespace {
Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(
nsIFile& aBaseDir, const nsID& aId, const BodyFileType aType) {
QM_TRY_UNWRAP(auto bodyFile, BodyGetCacheDir(aBaseDir, aId));
char idString[NSID_LENGTH];
aId.ToProvidedString(idString);
NS_ConvertASCIItoUTF16 fileName(idString);
if (aType == BODY_FILE_FINAL) {
fileName.AppendLiteral(".final");
} else {
fileName.AppendLiteral(".tmp");
}
QM_TRY(MOZ_TO_RESULT(bodyFile->Append(fileName)));
return bodyFile;
}
int64_t RoundUp(const int64_t aX, const int64_t aY) {
MOZ_DIAGNOSTIC_ASSERT(aX >= 0);
MOZ_DIAGNOSTIC_ASSERT(aY > 0);
MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY);
return aY + ((aX - 1) / aY) * aY;
}
int64_t BodyGeneratePadding(const int64_t aBodyFileSize,
const uint32_t aPaddingInfo) {
// Generate padding
int64_t randomSize = static_cast<int64_t>(aPaddingInfo);
MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize);
randomSize += aBodyFileSize;
return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize;
}
nsresult DirectoryPaddingWrite(nsIFile& aBaseDir,
DirPaddingFile aPaddingFileType,
int64_t aPaddingSize) {
MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0);
QM_TRY_INSPECT(
const auto& file,
CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
? nsLiteralString(PADDING_TMP_FILE_NAME)
: nsLiteralString(PADDING_FILE_NAME)));
QM_TRY_INSPECT(const auto& outputStream, NS_NewLocalFileOutputStream(file));
nsCOMPtr<nsIObjectOutputStream> objectStream =
NS_NewObjectOutputStream(outputStream);
QM_TRY(MOZ_TO_RESULT(objectStream->Write64(aPaddingSize)));
return NS_OK;
}
} // namespace
nsresult BodyDeleteOrphanedFiles(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsTArray<nsID>& aKnownBodyIdList) {
// body files are stored in a directory structure like:
//
// /morgue/01/{01fdddb2-884d-4c3d-95ba-0c8062f6c325}.final
// /morgue/02/{02fdddb2-884d-4c3d-95ba-0c8062f6c325}.tmp
QM_TRY_INSPECT(const auto& dir,
CloneFileAndAppend(aBaseDir, kMorgueDirectory));
// Iterate over all the intermediate morgue subdirs
QM_TRY(quota::CollectEachFile(
*dir,
[&aDirectoryMetadata, &aKnownBodyIdList](
const nsCOMPtr<nsIFile>& subdir) -> Result<Ok, nsresult> {
QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*subdir));
switch (dirEntryKind) {
case nsIFileKind::ExistsAsDirectory: {
const auto removeOrphanedFiles =
[&aDirectoryMetadata, &aKnownBodyIdList](
nsIFile& bodyFile,
const nsACString& leafName) -> Result<bool, nsresult> {
// Finally, parse the uuid out of the name. If it fails to parse,
// then ignore the file.
auto cleanup = MakeScopeExit([&aDirectoryMetadata, &bodyFile] {
DebugOnly<nsresult> result =
RemoveNsIFile(aDirectoryMetadata, bodyFile);
MOZ_ASSERT(NS_SUCCEEDED(result));
});
nsID id;
QM_TRY(OkIf(id.Parse(leafName.BeginReading())), true);
if (!aKnownBodyIdList.Contains(id)) {
return true;
}
cleanup.release();
return false;
};
// QM_OR_ELSE_WARN_IF is not used here since we just want to log
// NS_ERROR_FILE_FS_CORRUPTED result and not spam the reports (even
// a warning in the reports is not desired).
QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(BodyTraverseFiles(aDirectoryMetadata, *subdir,
removeOrphanedFiles,
/* aCanRemoveFiles */ true,
/* aTrackQuota */ true)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_FS_CORRUPTED>,
// Fallback. We treat NS_ERROR_FILE_FS_CORRUPTED as if the
// directory did not exist at all.
ErrToDefaultOk<>));
break;
}
case nsIFileKind::ExistsAsFile: {
// If a file got in here somehow, try to remove it and move on
DebugOnly<nsresult> result =
RemoveNsIFile(aDirectoryMetadata, *subdir,
/* aTrackQuota */ false);
MOZ_ASSERT(NS_SUCCEEDED(result));
break;
}
case nsIFileKind::DoesNotExist:
// Ignore files that got removed externally while iterating.
break;
}
return Ok{};
}));
return NS_OK;
}
namespace {
Result<nsCOMPtr<nsIFile>, nsresult> GetMarkerFileHandle(
const CacheDirectoryMetadata& aDirectoryMetadata) {
QM_TRY_UNWRAP(auto marker,
CloneFileAndAppend(*aDirectoryMetadata.mDir, u"cache"_ns));
QM_TRY(MOZ_TO_RESULT(marker->Append(u"context_open.marker"_ns)));
return marker;
}
} // namespace
nsresult CreateMarkerFile(const CacheDirectoryMetadata& aDirectoryMetadata) {
QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata));
// Callers call this function without checking if the file already exists
// (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we just want
// to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the reports.
//
// TODO: In theory if this file exists, then Context::~Context should have
// cleaned it up, but obviously we can crash and not clean it up, which is
// the whole point of the marker file. In that case, we'll realize the marker
// file exists in SetupAction::RunSyncWithDBOnTarget and do some cleanup, but
// we won't delete the marker file, so if we see this marker file, it is part
// of our standard operating procedure to redundantly try and create the
// marker here. We currently treat this as idempotent usage, but we could
// make sure to delete the marker file when handling the existing marker
// file in SetupAction::RunSyncWithDBOnTarget and change
// QM_OR_ELSE_LOG_VERBOSE_IF to QM_OR_ELSE_WARN_IF in the end.
QM_TRY(QM_OR_ELSE_LOG_VERBOSE_IF(
// Expression.
MOZ_TO_RESULT(marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644)),
// Predicate.
IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
// Fallback.
ErrToDefaultOk<>));
// Note, we don't need to fsync here. We only care about actually
// writing the marker if later modifications to the Cache are
// actually flushed to the disk. If the OS crashes before the marker
// is written then we are ensured no other changes to the Cache were
// flushed either.
return NS_OK;
}
nsresult DeleteMarkerFile(const CacheDirectoryMetadata& aDirectoryMetadata) {
QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata));
DebugOnly<nsresult> result =
RemoveNsIFile(aDirectoryMetadata, *marker, /* aTrackQuota */ false);
MOZ_ASSERT(NS_SUCCEEDED(result));
// Again, no fsync is necessary. If the OS crashes before the file
// removal is flushed, then the Cache will search for stale data on
// startup. This will cause the next Cache access to be a bit slow, but
// it seems appropriate after an OS crash.
return NS_OK;
}
bool MarkerFileExists(const CacheDirectoryMetadata& aDirectoryMetadata) {
QM_TRY_INSPECT(const auto& marker, GetMarkerFileHandle(aDirectoryMetadata),
false);
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(marker, Exists), false);
}
nsresult RemoveNsIFileRecursively(
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, nsIFile& aFile,
const bool aTrackQuota) {
// XXX This assertion proves that we can remove aTrackQuota and just check
// aClientMetadata
MOZ_DIAGNOSTIC_ASSERT_IF(aTrackQuota, aDirectoryMetadata);
QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(aFile));
switch (dirEntryKind) {
case nsIFileKind::ExistsAsDirectory:
// Unfortunately, we need to traverse all the entries and delete files one
// by
// one to update their usages to the QuotaManager.
QM_TRY(quota::CollectEachFile(
aFile,
[&aDirectoryMetadata, &aTrackQuota](
const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
QM_TRY(MOZ_TO_RESULT(RemoveNsIFileRecursively(aDirectoryMetadata,
*file, aTrackQuota)));
return Ok{};
}));
// In the end, remove the folder
QM_TRY(MOZ_TO_RESULT(aFile.Remove(/* recursive */ false)));
break;
case nsIFileKind::ExistsAsFile:
return RemoveNsIFile(aDirectoryMetadata, aFile, aTrackQuota);
case nsIFileKind::DoesNotExist:
// Ignore files that got removed externally while iterating.
break;
}
return NS_OK;
}
nsresult RemoveNsIFile(const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
nsIFile& aFile, const bool aTrackQuota) {
// XXX This assertion proves that we can remove aTrackQuota and just check
// aClientMetadata
MOZ_DIAGNOSTIC_ASSERT_IF(aTrackQuota, aDirectoryMetadata);
int64_t fileSize = 0;
if (aTrackQuota) {
QM_TRY_INSPECT(
const auto& maybeFileSize,
QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize).map(Some<int64_t>),
// Predicate.
IsFileNotFoundError,
// Fallback.
ErrToDefaultOk<Maybe<int64_t>>));
if (!maybeFileSize) {
return NS_OK;
}
fileSize = *maybeFileSize;
}
QM_TRY(QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT(aFile.Remove(/* recursive */ false)),
// Predicate.
IsFileNotFoundError,
// Fallback.
ErrToDefaultOk<>));
if (fileSize > 0) {
MOZ_ASSERT(aTrackQuota);
DecreaseUsageForDirectoryMetadata(*aDirectoryMetadata, fileSize);
}
return NS_OK;
}
void DecreaseUsageForDirectoryMetadata(
const CacheDirectoryMetadata& aDirectoryMetadata,
const int64_t aUpdatingSize) {
MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0);
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_DIAGNOSTIC_ASSERT(quotaManager);
quotaManager->DecreaseUsageForClient(
quota::ClientMetadata{aDirectoryMetadata, Client::DOMCACHE},
aUpdatingSize);
}
bool DirectoryPaddingFileExists(nsIFile& aBaseDir,
DirPaddingFile aPaddingFileType) {
QM_TRY_INSPECT(
const auto& file,
CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
? nsLiteralString(PADDING_TMP_FILE_NAME)
: nsLiteralString(PADDING_FILE_NAME)),
false);
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists), false);
}
Result<int64_t, nsresult> DirectoryPaddingGet(nsIFile& aBaseDir) {
MOZ_DIAGNOSTIC_ASSERT(
!DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
QM_TRY_INSPECT(
const auto& file,
CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_FILE_NAME)));
QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file));
QM_TRY_INSPECT(const auto& bufferedStream,
NS_NewBufferedInputStream(stream.forget(), 512));
const nsCOMPtr<nsIObjectInputStream> objectStream =
NS_NewObjectInputStream(bufferedStream);
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(objectStream, Read64)
.map([](const uint64_t val) { return int64_t(val); }));
}
nsresult DirectoryPaddingInit(nsIFile& aBaseDir) {
QM_TRY(
MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0)));
return NS_OK;
}
nsresult UpdateDirectoryPaddingFile(nsIFile& aBaseDir,
mozIStorageConnection& aConn,
const int64_t aIncreaseSize,
const int64_t aDecreaseSize,
const bool aTemporaryFileExist) {
MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
const auto directoryPaddingGetResult =
aTemporaryFileExist ? Maybe<int64_t>{} : [&aBaseDir] {
QM_TRY_RETURN(QM_OR_ELSE_WARN_IF(
// Expression.
DirectoryPaddingGet(aBaseDir).map(Some<int64_t>),
// Predicate.
IsFileNotFoundError,
// Fallback.
ErrToDefaultOk<Maybe<int64_t>>),
Maybe<int64_t>{});
}();
QM_TRY_INSPECT(
const int64_t& currentPaddingSize,
([directoryPaddingGetResult, &aBaseDir, &aConn, aIncreaseSize,
aDecreaseSize]() -> Result<int64_t, nsresult> {
if (!directoryPaddingGetResult) {
// Fail to read padding size from the dir padding file, so try to
// restore.
// Not delete the temporary padding file here, because we're going
// to overwrite it below anyway.
QM_TRY(MOZ_TO_RESULT(
DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE)));
// We don't need to add the aIncreaseSize or aDecreaseSize here,
// because it's already encompassed within the database.
QM_TRY_RETURN(db::FindOverallPaddingSize(aConn));
}
int64_t currentPaddingSize = directoryPaddingGetResult.value();
bool shouldRevise = false;
if (aIncreaseSize > 0) {
if (INT64_MAX - currentPaddingSize < aDecreaseSize) {
shouldRevise = true;
} else {
currentPaddingSize += aIncreaseSize;
}
}
if (aDecreaseSize > 0) {
if (currentPaddingSize < aDecreaseSize) {
shouldRevise = true;
} else if (!shouldRevise) {
currentPaddingSize -= aDecreaseSize;
}
}
if (shouldRevise) {
// If somehow runing into this condition, the tracking padding size is
// incorrect.
// Delete padding file to indicate the padding size is incorrect for
// avoiding error happening in the following lines.
QM_TRY(MOZ_TO_RESULT(
DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE)));
QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn));
// XXXtt: we should have an easy way to update (increase or
// recalulate) padding size in the QM. For now, only correct the
// padding size in padding file and make QM be able to get the correct
// size in the next QM initialization. We still want to catch this in
// the debug build.
MOZ_ASSERT(false, "The padding size is unsync with QM");
}
#ifdef DEBUG
const int64_t lastPaddingSize = currentPaddingSize;
QM_TRY_UNWRAP(currentPaddingSize, db::FindOverallPaddingSize(aConn));
MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize == lastPaddingSize);
#endif // DEBUG
return currentPaddingSize;
}()));
MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0);
QM_TRY(MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE,
currentPaddingSize)));
return NS_OK;
}
nsresult DirectoryPaddingFinalizeWrite(nsIFile& aBaseDir) {
MOZ_DIAGNOSTIC_ASSERT(
DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE));
QM_TRY_INSPECT(
const auto& file,
CloneFileAndAppend(aBaseDir, nsLiteralString(PADDING_TMP_FILE_NAME)));
QM_TRY(MOZ_TO_RESULT(
file->RenameTo(nullptr, nsLiteralString(PADDING_FILE_NAME))));
return NS_OK;
}
Result<int64_t, nsresult> DirectoryPaddingRestore(nsIFile& aBaseDir,
mozIStorageConnection& aConn,
const bool aMustRestore) {
// The content of padding file is untrusted, so remove it here.
QM_TRY(MOZ_TO_RESULT(
DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE)));
QM_TRY_INSPECT(const int64_t& paddingSize, db::FindOverallPaddingSize(aConn));
MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
QM_TRY(MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE,
paddingSize)),
(aMustRestore ? Err(tryTempError)
: Result<int64_t, nsresult>{paddingSize}));
QM_TRY(MOZ_TO_RESULT(
DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE)));
return paddingSize;
}
nsresult DirectoryPaddingDeleteFile(nsIFile& aBaseDir,
DirPaddingFile aPaddingFileType) {
QM_TRY_INSPECT(
const auto& file,
CloneFileAndAppend(aBaseDir, aPaddingFileType == DirPaddingFile::TMP_FILE
? nsLiteralString(PADDING_TMP_FILE_NAME)
: nsLiteralString(PADDING_FILE_NAME)));
QM_TRY(QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT(file->Remove(/* recursive */ false)),
// Predicate.
IsFileNotFoundError,
// Fallback.
ErrToDefaultOk<>));
return NS_OK;
}
} // namespace mozilla::dom::cache