/* -*- 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 "mozilla/dom/cache/FileUtils.h" #include "DBSchema.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/quota/FileStreams.h" #include "mozilla/dom/quota/QuotaManager.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 "nsISimpleEnumerator.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsThreadUtils.h" namespace mozilla { namespace dom { namespace cache { using mozilla::dom::quota::FileInputStream; using mozilla::dom::quota::FileOutputStream; 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 }; nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, nsIFile** aBodyFileOut); int64_t RoundUp(const int64_t aX, const 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(const int64_t aBodyFileSize, const uint32_t aPaddingInfo); nsresult LockedDirectoryPaddingWrite(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType, int64_t aPaddingSize); } // namespace // static nsresult BodyCreateDir(nsIFile* aBaseDir) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsCOMPtr aBodyDir; nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aBodyDir->Append(NS_LITERAL_STRING("morgue")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aBodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755); if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult BodyDeleteDir(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsCOMPtr aBodyDir; nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aBodyDir->Append(NS_LITERAL_STRING("morgue")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = RemoveNsIFileRecursively(aQuotaInfo, aBodyDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, nsIFile** aCacheDirOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aCacheDirOut); *aCacheDirOut = nullptr; nsresult rv = aBaseDir->Clone(aCacheDirOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(*aCacheDirOut); rv = (*aCacheDirOut)->Append(NS_LITERAL_STRING("morgue")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // 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. nsAutoString subDirName; subDirName.AppendInt(aId.m3[7]); rv = (*aCacheDirOut)->Append(subDirName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = (*aCacheDirOut)->Create(nsIFile::DIRECTORY_TYPE, 0755); if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, nsIInputStream* aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback, nsID* aIdOut, nsISupports** aCopyContextOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aSource); MOZ_DIAGNOSTIC_ASSERT(aClosure); MOZ_DIAGNOSTIC_ASSERT(aCallback); MOZ_DIAGNOSTIC_ASSERT(aIdOut); MOZ_DIAGNOSTIC_ASSERT(aCopyContextOut); nsresult rv; nsCOMPtr idGen = do_GetService("@mozilla.org/uuid-generator;1", &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = idGen->GenerateUUIDInPlace(aIdOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr finalFile; rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_FINAL, getter_AddRefs(finalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = finalFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; } nsCOMPtr tmpFile; rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_TMP, getter_AddRefs(tmpFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = tmpFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; } nsCOMPtr fileStream = CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, tmpFile); if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; } RefPtr compressed = new SnappyCompressOutputStream(fileStream); nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); rv = NS_AsyncCopy(aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS, compressed->BlockSize(), aCallback, aClosure, true, true, // close streams aCopyContextOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static void BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aCopyContext); nsresult rv = NS_CancelAsyncCopy(aCopyContext, NS_ERROR_ABORT); Unused << NS_WARN_IF(NS_FAILED(rv)); // The partially written file must be cleaned up after the async copy // makes its callback. } // static nsresult BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsCOMPtr tmpFile; nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr finalFile; rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoString finalFileName; rv = finalFile->GetLeafName(finalFileName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // 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. rv = tmpFile->RenameTo(nullptr, finalFileName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, nsIInputStream** aStreamOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aStreamOut); nsCOMPtr finalFile; nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool exists; rv = finalFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!exists)) { return NS_ERROR_FILE_NOT_FOUND; } nsCOMPtr fileStream = CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, finalFile); if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; } fileStream.forget(aStreamOut); return rv; } // static nsresult BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, const uint32_t aPaddingInfo, int64_t* aPaddingSizeOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut); nsCOMPtr bodyFile; nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(bodyFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(bodyFile); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_DIAGNOSTIC_ASSERT(quotaManager); int64_t fileSize = 0; RefPtr quotaObject = quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, bodyFile, &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 (*aPaddingSizeOut == InternalResponse::UNKNOWN_PADDING_SIZE) { *aPaddingSizeOut = BodyGeneratePadding(fileSize, aPaddingInfo); } MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeOut >= 0); if (!quotaObject->IncreaseSize(*aPaddingSizeOut)) { return NS_ERROR_FILE_NO_DEVICE_SPACE; } return rv; } // static nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsTArray& aIdList) { nsresult rv = NS_OK; for (uint32_t i = 0; i < aIdList.Length(); ++i) { nsCOMPtr tmpFile; rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_TMP, getter_AddRefs(tmpFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = RemoveNsIFile(aQuotaInfo, tmpFile); // Only treat file deletion as a hard failure in DEBUG builds. Users // can unfortunately hit this on windows if anti-virus is scanning files, // etc. MOZ_ASSERT(NS_SUCCEEDED(rv)); nsCOMPtr finalFile; rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_FINAL, getter_AddRefs(finalFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = RemoveNsIFile(aQuotaInfo, finalFile); // Again, only treat removal as hard failure in debug build. MOZ_ASSERT(NS_SUCCEEDED(rv)); } return NS_OK; } namespace { nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, nsIFile** aBodyFileOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aBodyFileOut); *aBodyFileOut = nullptr; nsresult rv = BodyGetCacheDir(aBaseDir, aId, aBodyFileOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(*aBodyFileOut); char idString[NSID_LENGTH]; aId.ToProvidedString(idString); NS_ConvertASCIItoUTF16 fileName(idString); if (aType == BODY_FILE_FINAL) { fileName.AppendLiteral(".final"); } else { fileName.AppendLiteral(".tmp"); } rv = (*aBodyFileOut)->Append(fileName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } 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 LockedDirectoryPaddingWrite(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType, int64_t aPaddingSize) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0); nsCOMPtr file; nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aPaddingFileType == DirPaddingFile::TMP_FILE) { rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); } else { rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr outputStream; rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr objectStream = NS_NewObjectOutputStream(outputStream); rv = objectStream->Write64(aPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } } // namespace nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, nsTArray& aKnownBodyIdList) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); // 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 nsCOMPtr dir; nsresult rv = aBaseDir->Clone(getter_AddRefs(dir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Add the root morgue directory rv = dir->Append(NS_LITERAL_STRING("morgue")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr entries; rv = dir->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Iterate over all the intermediate morgue subdirs nsCOMPtr subdir; while (NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(subdir))) && subdir) { bool isDir = false; rv = subdir->IsDirectory(&isDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // If a file got in here somehow, try to remove it and move on if (NS_WARN_IF(!isDir)) { rv = RemoveNsIFile(aQuotaInfo, subdir); MOZ_ASSERT(NS_SUCCEEDED(rv)); continue; } nsCOMPtr subEntries; rv = subdir->GetDirectoryEntries(getter_AddRefs(subEntries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Now iterate over all the files in the subdir nsCOMPtr file; while(NS_SUCCEEDED(rv = subEntries->GetNextFile(getter_AddRefs(file))) && file) { nsAutoCString leafName; rv = file->GetNativeLeafName(leafName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Delete all tmp files regardless of known bodies. These are // all considered orphans. if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".tmp"))) { // remove recursively in case its somehow a directory rv = RemoveNsIFileRecursively(aQuotaInfo, file); MOZ_ASSERT(NS_SUCCEEDED(rv)); continue; } nsCString suffix(NS_LITERAL_CSTRING(".final")); // Otherwise, it must be a .final file. If its not, then just // skip it. if (NS_WARN_IF(!StringEndsWith(leafName, suffix) || leafName.Length() != NSID_LENGTH - 1 + suffix.Length())) { continue; } // Finally, parse the uuid out of the name. If its fails to parse, // the ignore the file. nsID id; if (NS_WARN_IF(!id.Parse(leafName.BeginReading()))) { continue; } if (!aKnownBodyIdList.Contains(id)) { // remove recursively in case its somehow a directory rv = RemoveNsIFileRecursively(aQuotaInfo, file); MOZ_ASSERT(NS_SUCCEEDED(rv)); } } } return rv; } namespace { nsresult GetMarkerFileHandle(const QuotaInfo& aQuotaInfo, nsIFile** aFileOut) { MOZ_DIAGNOSTIC_ASSERT(aFileOut); nsCOMPtr marker; nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(marker)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = marker->Append(NS_LITERAL_STRING("cache")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = marker->Append(NS_LITERAL_STRING("context_open.marker")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } marker.forget(aFileOut); return rv; } } // namespace nsresult CreateMarkerFile(const QuotaInfo& aQuotaInfo) { nsCOMPtr marker; nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = marker->Create(nsIFile::NORMAL_FILE_TYPE, 0644); if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { rv = NS_OK; } // 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 rv; } nsresult DeleteMarkerFile(const QuotaInfo& aQuotaInfo) { nsCOMPtr marker; nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = RemoveNsIFile(aQuotaInfo, marker); MOZ_ASSERT(NS_SUCCEEDED(rv)); // 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 QuotaInfo& aQuotaInfo) { nsCOMPtr marker; nsresult rv = GetMarkerFileHandle(aQuotaInfo, getter_AddRefs(marker)); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } bool exists = false; rv = marker->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return exists; } // static nsresult RemoveNsIFileRecursively(const QuotaInfo& aQuotaInfo, nsIFile* aFile) { MOZ_DIAGNOSTIC_ASSERT(aFile); bool isDirectory = false; nsresult rv = aFile->IsDirectory(&isDirectory); if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isDirectory) { return RemoveNsIFile(aQuotaInfo, aFile); } // Unfortunately, we need to traverse all the entries and delete files one by // one to update their usages to the QuotaManager. nsCOMPtr entries; rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr file; while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) && file) { rv = RemoveNsIFileRecursively(aQuotaInfo, file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // In the end, remove the folder rv = aFile->Remove(/* recursive */ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult RemoveNsIFile(const QuotaInfo& aQuotaInfo, nsIFile* aFile) { MOZ_DIAGNOSTIC_ASSERT(aFile); int64_t fileSize = 0; nsresult rv = aFile->GetFileSize(&fileSize); if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aFile->Remove( /* recursive */ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (fileSize > 0) { DecreaseUsageForQuotaInfo(aQuotaInfo, fileSize); } return rv; } // static void DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo, const int64_t& aUpdatingSize) { MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0); QuotaManager* quotaManager = QuotaManager::Get(); MOZ_DIAGNOSTIC_ASSERT(quotaManager); quotaManager->DecreaseUsageForOrigin(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, aUpdatingSize); } // static bool DirectoryPaddingFileExists(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsCOMPtr file; nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } nsString fileName; if (aPaddingFileType == DirPaddingFile::TMP_FILE) { fileName = NS_LITERAL_STRING(PADDING_TMP_FILE_NAME); } else { fileName = NS_LITERAL_STRING(PADDING_FILE_NAME); } rv = file->Append(fileName); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } bool exists = false; rv = file->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return exists; } // static nsresult LockedDirectoryPaddingGet(nsIFile* aBaseDir, int64_t* aPaddingSizeOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut); MOZ_DIAGNOSTIC_ASSERT(!DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE)); nsCOMPtr file; nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr stream; rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr bufferedStream; rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream.forget(), 512); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr objectStream = NS_NewObjectInputStream(bufferedStream); uint64_t paddingSize = 0; rv = objectStream->Read64(&paddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aPaddingSizeOut = paddingSize; return rv; } // static nsresult LockedDirectoryPaddingInit(nsIFile* aBaseDir) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0); Unused << NS_WARN_IF(NS_FAILED(rv)); return rv; } // static nsresult LockedUpdateDirectoryPaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn, const int64_t aIncreaseSize, const int64_t aDecreaseSize, const bool aTemporaryFileExist) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aConn); MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); int64_t currentPaddingSize = 0; nsresult rv = NS_OK; if (aTemporaryFileExist || NS_WARN_IF(NS_FAILED(rv = LockedDirectoryPaddingGet(aBaseDir, ¤tPaddingSize)))) { // Fail to read padding size from the dir padding file, so try to restore. if (rv != NS_ERROR_FILE_NOT_FOUND && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { // Not delete the temporary padding file here, because we're going to // overwrite it below anyway. rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // We don't need to add the aIncreaseSize or aDecreaseSize here, because // it's already encompassed within the database. rv = db::FindOverallPaddingSize(aConn, ¤tPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { 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. rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t paddingSizeFromDB = 0; rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } currentPaddingSize = paddingSizeFromDB; // 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 int64_t paddingSizeFromDB = 0; rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(paddingSizeFromDB == currentPaddingSize); #endif // DEBUG } MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0); rv = LockedDirectoryPaddingTemporaryWrite(aBaseDir, currentPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult LockedDirectoryPaddingTemporaryWrite(nsIFile* aBaseDir, int64_t aPaddingSize) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0); nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE, aPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult LockedDirectoryPaddingFinalizeWrite(nsIFile* aBaseDir) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE)); nsCOMPtr file; nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->RenameTo(nullptr, NS_LITERAL_STRING(PADDING_FILE_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } // static nsresult LockedDirectoryPaddingRestore(nsIFile* aBaseDir, mozIStorageConnection* aConn, bool aMustRestore, int64_t* aPaddingSizeOut) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); MOZ_DIAGNOSTIC_ASSERT(aConn); MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut); // The content of padding file is untrusted, so remove it here. nsresult rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t paddingSize = 0; rv = db::FindOverallPaddingSize(aConn, &paddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); *aPaddingSizeOut = paddingSize; rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { // If we cannot write the correct padding size to file, just keep the // temporary file and let the padding size to be recalculate in the next // action return aMustRestore ? rv : NS_OK; } rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE); Unused << NS_WARN_IF(NS_FAILED(rv)); return rv; } // static nsresult LockedDirectoryPaddingDeleteFile(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); nsCOMPtr file; nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aPaddingFileType == DirPaddingFile::TMP_FILE) { rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); } else { rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = file->Remove( /* recursive */ false); if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } } // namespace cache } // namespace dom } // namespace mozilla