/* -*- 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 "FileUtilsImpl.h" #include "DBSchema.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/quota/FileStreams.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 == 0x010109); using mozilla::dom::quota::Client; using mozilla::dom::quota::CloneFileAndAppend; using mozilla::dom::quota::FileInputStream; using mozilla::dom::quota::FileOutputStream; using mozilla::dom::quota::GetDirEntryKind; using mozilla::dom::quota::nsIFileKind; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::QuotaObject; namespace { // Const variable for generate padding size. // XXX This will be tweaked to something more meaningful in Bug 1383656. const int64_t kRoundUpNumber = 20480; enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP }; Result>, 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 // https://github.com/whatwg/storage/issues/31. // 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>, 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, // 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, // 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>, nsresult> BodyStartWriteStream( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) { MOZ_DIAGNOSTIC_ASSERT(aClosure); MOZ_DIAGNOSTIC_ASSERT(aCallback); QM_TRY_INSPECT(const auto& idGen, MOZ_TO_RESULT_GET_TYPED(nsCOMPtr, MOZ_SELECT_OVERLOAD(do_GetService), "@mozilla.org/uuid-generator;1")); nsID id; QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&id))); QM_TRY_INSPECT(const auto& finalFile, BodyIdToFile(aBaseDir, id, 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, id, BODY_FILE_TMP)); QM_TRY_UNWRAP( nsCOMPtr fileStream, CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, Client::DOMCACHE, tmpFile.get())); const auto compressed = MakeRefPtr(fileStream); const nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); nsCOMPtr 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::make_pair(id, 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. } 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))); return NS_OK; } Result>, nsresult> BodyOpen( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, const nsID& aId) { QM_TRY_INSPECT(const auto& finalFile, BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); QM_TRY_RETURN(CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, Client::DOMCACHE, finalFile.get())); } 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 = quotaManager->GetQuotaObject( PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, Client::DOMCACHE, bodyFile.get(), -1, &fileSize); MOZ_DIAGNOSTIC_ASSERT(quotaObject); MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0); // XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815 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& 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 { nsID fileId; QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true, ([&aDirectoryMetadata, &bodyFile](const auto) { DebugOnly result = RemoveNsIFile( aDirectoryMetadata, bodyFile, /* aTrackQuota */ false); MOZ_ASSERT(NS_SUCCEEDED(result)); })); if (id.Equals(fileId)) { DebugOnly 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>, 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(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 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& 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& subdir) -> Result { QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*subdir)); switch (dirEntryKind) { case nsIFileKind::ExistsAsDirectory: { const auto removeOrphanedFiles = [&aDirectoryMetadata, &aKnownBodyIdList]( nsIFile& bodyFile, const nsACString& leafName) -> Result { // Finally, parse the uuid out of the name. If it fails to parse, // then ignore the file. auto cleanup = MakeScopeExit([&aDirectoryMetadata, &bodyFile] { DebugOnly 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, // 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 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, 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, // 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 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& 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& file) -> Result { 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& 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), // Predicate. IsFileNotFoundError, // Fallback. ErrToDefaultOk>)); 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 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 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{} : [&aBaseDir] { QM_TRY_RETURN(QM_OR_ELSE_WARN_IF( // Expression. DirectoryPaddingGet(aBaseDir).map(Some), // Predicate. IsFileNotFoundError, // Fallback. ErrToDefaultOk>), Maybe{}); }(); QM_TRY_INSPECT( const int64_t& currentPaddingSize, ([directoryPaddingGetResult, &aBaseDir, &aConn, aIncreaseSize, aDecreaseSize]() -> Result { 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 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{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