gecko-dev/dom/cache/FileUtils.cpp

936 lines
28 KiB
C++

/* -*- 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<nsIFile> 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<nsIFile> 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<nsIUUIDGenerator> 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<nsIFile> 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<nsIFile> 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<nsIOutputStream> fileStream =
CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
aQuotaInfo.mOrigin, tmpFile);
if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
RefPtr<SnappyCompressOutputStream> compressed =
new SnappyCompressOutputStream(fileStream);
nsCOMPtr<nsIEventTarget> 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<nsIFile> tmpFile;
nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> 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<nsIFile> 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<nsIInputStream> 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<nsIFile> 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> 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<nsID>& aIdList)
{
nsresult rv = NS_OK;
for (uint32_t i = 0; i < aIdList.Length(); ++i) {
nsCOMPtr<nsIFile> 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<nsIFile> 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<int64_t>(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<nsIFile> 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<nsIOutputStream> outputStream;
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIObjectOutputStream> 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<nsID>& 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<nsIFile> 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<nsIDirectoryEnumerator> entries;
rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Iterate over all the intermediate morgue subdirs
nsCOMPtr<nsIFile> 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<nsIDirectoryEnumerator> 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<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIDirectoryEnumerator> entries;
rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIFile> 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<nsIFile> 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<nsIFile> 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<nsIInputStream> stream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIInputStream> bufferedStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream.forget(),
512);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<nsIObjectInputStream> 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, &currentPaddingSize)))) {
// 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, &currentPaddingSize);
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<nsIFile> 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<nsIFile> 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