diff options
Diffstat (limited to 'dom/cache/FileUtils.cpp')
-rw-r--r-- | dom/cache/FileUtils.cpp | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/dom/cache/FileUtils.cpp b/dom/cache/FileUtils.cpp new file mode 100644 index 0000000000..dce98ac1c4 --- /dev/null +++ b/dom/cache/FileUtils.cpp @@ -0,0 +1,501 @@ +/* -*- 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 "mozilla/dom/quota/FileStreams.h" +#include "mozilla/SnappyCompressOutputStream.h" +#include "mozilla/Unused.h" +#include "nsIFile.h" +#include "nsIUUIDGenerator.h" +#include "nsNetCID.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; + +namespace { + +enum BodyFileType +{ + BODY_FILE_FINAL, + BODY_FILE_TMP +}; + +nsresult +BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, + nsIFile** aBodyFileOut); + +} // 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(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->Remove(/* recursive = */ true); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + rv = NS_OK; + } + 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 = + FileOutputStream::Create(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; } + + 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 = + FileInputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, + aQuotaInfo.mOrigin, finalFile); + if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; } + + fileStream.forget(aStreamOut); + + return rv; +} + +// static +nsresult +BodyDeleteFiles(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 = tmpFile->Remove(false /* recursive */); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + rv = NS_OK; + } + + // 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 = finalFile->Remove(false /* recursive */); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + rv = NS_OK; + } + + // 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; +} + +} // namespace + +nsresult +BodyDeleteOrphanedFiles(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<nsISimpleEnumerator> entries; + rv = dir->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Iterate over all the intermediate morgue subdirs + bool hasMore = false; + while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIFile> subdir = do_QueryInterface(entry); + + 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 = subdir->Remove(false /* recursive */); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + continue; + } + + nsCOMPtr<nsISimpleEnumerator> 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 + bool subHasMore = false; + while(NS_SUCCEEDED(rv = subEntries->HasMoreElements(&subHasMore)) && + subHasMore) { + nsCOMPtr<nsISupports> subEntry; + rv = subEntries->GetNext(getter_AddRefs(subEntry)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIFile> file = do_QueryInterface(subEntry); + + 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 = file->Remove(true /* recursive */); + 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 = file->Remove(true /* recursive */); + 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 = marker->Remove(/* recursive = */ false); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + rv = NS_OK; + } + + // 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; +} + +} // namespace cache +} // namespace dom +} // namespace mozilla |