diff options
Diffstat (limited to 'dom/quota/QuotaCommon.cpp')
-rw-r--r-- | dom/quota/QuotaCommon.cpp | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/dom/quota/QuotaCommon.cpp b/dom/quota/QuotaCommon.cpp new file mode 100644 index 0000000000..16e73f4ed6 --- /dev/null +++ b/dom/quota/QuotaCommon.cpp @@ -0,0 +1,439 @@ +/* -*- 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 "QuotaCommon.h" + +#include "mozIStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryComms.h" +#include "mozilla/TelemetryEventEnums.h" +#include "mozilla/TextUtils.h" +#include "nsIConsoleService.h" +#include "nsIFile.h" +#include "nsServiceManagerUtils.h" +#include "nsStringFlags.h" +#include "nsTStringRepr.h" +#include "nsUnicharUtils.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" + +#ifdef XP_WIN +# include "mozilla/Atomics.h" +# include "mozilla/ipc/BackgroundParent.h" +# include "mozilla/StaticPrefs_dom.h" +# include "nsILocalFileWin.h" +#endif + +namespace mozilla::dom::quota { + +using namespace mozilla::Telemetry; + +namespace { + +#ifdef DEBUG +constexpr auto kDSStoreFileName = u".DS_Store"_ns; +constexpr auto kDesktopFileName = u".desktop"_ns; +constexpr auto kDesktopIniFileName = u"desktop.ini"_ns; +constexpr auto kThumbsDbFileName = u"thumbs.db"_ns; +#endif + +#ifdef XP_WIN +Atomic<int32_t> gUseDOSDevicePathSyntax(-1); +#endif + +LazyLogModule gLogger("QuotaManager"); + +void AnonymizeCString(nsACString& aCString, uint32_t aStart) { + MOZ_ASSERT(!aCString.IsEmpty()); + MOZ_ASSERT(aStart < aCString.Length()); + + char* iter = aCString.BeginWriting() + aStart; + char* end = aCString.EndWriting(); + + while (iter != end) { + char c = *iter; + + if (IsAsciiAlpha(c)) { + *iter = 'a'; + } else if (IsAsciiDigit(c)) { + *iter = 'D'; + } + + ++iter; + } +} + +} // namespace + +const char kQuotaGenericDelimiter = '|'; + +#ifdef NIGHTLY_BUILD +const nsLiteralCString kQuotaInternalError = "internal"_ns; +const nsLiteralCString kQuotaExternalError = "external"_ns; +#endif + +LogModule* GetQuotaManagerLogger() { return gLogger; } + +void AnonymizeCString(nsACString& aCString) { + if (aCString.IsEmpty()) { + return; + } + AnonymizeCString(aCString, /* aStart */ 0); +} + +void AnonymizeOriginString(nsACString& aOriginString) { + if (aOriginString.IsEmpty()) { + return; + } + + int32_t start = aOriginString.FindChar(':'); + if (start < 0) { + start = 0; + } + + AnonymizeCString(aOriginString, start); +} + +#ifdef XP_WIN +void CacheUseDOSDevicePathSyntaxPrefValue() { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertIsOnBackgroundThread(); + + if (gUseDOSDevicePathSyntax == -1) { + bool useDOSDevicePathSyntax = + StaticPrefs::dom_quotaManager_useDOSDevicePathSyntax_DoNotUseDirectly(); + gUseDOSDevicePathSyntax = useDOSDevicePathSyntax ? 1 : 0; + } +} +#endif + +Result<nsCOMPtr<nsIFile>, nsresult> QM_NewLocalFile(const nsAString& aPath) { + QM_TRY_UNWRAP(auto file, + ToResultInvoke<nsCOMPtr<nsIFile>>(NS_NewLocalFile, aPath, + /* aFollowLinks */ false), + QM_PROPAGATE, [&aPath](const nsresult rv) { + QM_WARNING("Failed to construct a file for path (%s)", + NS_ConvertUTF16toUTF8(aPath).get()); + }); + +#ifdef XP_WIN + MOZ_ASSERT(gUseDOSDevicePathSyntax != -1); + + if (gUseDOSDevicePathSyntax) { + QM_TRY_INSPECT(const auto& winFile, + ToResultGet<nsCOMPtr<nsILocalFileWin>>( + MOZ_SELECT_OVERLOAD(do_QueryInterface), file)); + + MOZ_ASSERT(winFile); + winFile->SetUseDOSDevicePathSyntax(true); + } +#endif + + return file; +} + +nsDependentCSubstring GetLeafName(const nsACString& aPath) { + nsACString::const_iterator start, end; + aPath.BeginReading(start); + aPath.EndReading(end); + + bool found = RFindInReadable("/"_ns, start, end); + if (found) { + start = end; + } + + aPath.EndReading(end); + + return nsDependentCSubstring(start.get(), end.get()); +} + +Result<nsCOMPtr<nsIFile>, nsresult> CloneFileAndAppend( + nsIFile& aDirectory, const nsAString& aPathElement) { + QM_TRY_UNWRAP(auto resultFile, MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIFile>, + aDirectory, Clone)); + + QM_TRY(resultFile->Append(aPathElement)); + + return resultFile; +} + +Result<nsIFileKind, nsresult> GetDirEntryKind(nsIFile& aFile) { + QM_TRY_RETURN( + MOZ_TO_RESULT_INVOKE(aFile, IsDirectory) + .map([](const bool isDirectory) { + return isDirectory ? nsIFileKind::ExistsAsDirectory + : nsIFileKind::ExistsAsFile; + }) + .orElse([](const nsresult rv) -> Result<nsIFileKind, nsresult> { + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + return nsIFileKind::DoesNotExist; + } + + return Err(rv); + })); +} + +Result<nsCOMPtr<mozIStorageStatement>, nsresult> CreateStatement( + mozIStorageConnection& aConnection, const nsACString& aStatementString) { + QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>, + aConnection, CreateStatement, + aStatementString)); +} + +template <SingleStepResult ResultHandling> +Result<SingleStepSuccessType<ResultHandling>, nsresult> ExecuteSingleStep( + nsCOMPtr<mozIStorageStatement>&& aStatement) { + QM_TRY_INSPECT(const bool& hasResult, + MOZ_TO_RESULT_INVOKE(aStatement, ExecuteStep)); + + if constexpr (ResultHandling == SingleStepResult::AssertHasResult) { + MOZ_ASSERT(hasResult); + (void)hasResult; + + return WrapNotNullUnchecked(std::move(aStatement)); + } else { + return hasResult ? std::move(aStatement) : nullptr; + } +} + +template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>, + nsresult> +ExecuteSingleStep<SingleStepResult::AssertHasResult>( + nsCOMPtr<mozIStorageStatement>&&); + +template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>, + nsresult> +ExecuteSingleStep<SingleStepResult::ReturnNullIfNoResult>( + nsCOMPtr<mozIStorageStatement>&&); + +template <SingleStepResult ResultHandling> +Result<SingleStepSuccessType<ResultHandling>, nsresult> +CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection, + const nsACString& aStatementString) { + QM_TRY_UNWRAP(auto stmt, MOZ_TO_RESULT_INVOKE_TYPED( + nsCOMPtr<mozIStorageStatement>, aConnection, + CreateStatement, aStatementString)); + + return ExecuteSingleStep<ResultHandling>(std::move(stmt)); +} + +template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>, + nsresult> +CreateAndExecuteSingleStepStatement<SingleStepResult::AssertHasResult>( + mozIStorageConnection& aConnection, const nsACString& aStatementString); + +template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>, + nsresult> +CreateAndExecuteSingleStepStatement<SingleStepResult::ReturnNullIfNoResult>( + mozIStorageConnection& aConnection, const nsACString& aStatementString); + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO +MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sQueryValue; +MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sContextValue; + +/* static */ +auto ScopedLogExtraInfo::FindSlot(const char* aTag) { + // XXX For now, don't use a real map but just allow the known tag values. + + if (aTag == kTagQuery) { + return &sQueryValue; + } + + if (aTag == kTagContext) { + return &sContextValue; + } + + MOZ_CRASH("Unknown tag!"); +} + +ScopedLogExtraInfo::~ScopedLogExtraInfo() { + if (mTag) { + MOZ_ASSERT(&mCurrentValue == FindSlot(mTag)->get(), + "Bad scoping of ScopedLogExtraInfo, must not be interleaved!"); + + FindSlot(mTag)->set(mPreviousValue); + } +} + +ScopedLogExtraInfo::ScopedLogExtraInfo(ScopedLogExtraInfo&& aOther) + : mTag(aOther.mTag), + mPreviousValue(aOther.mPreviousValue), + mCurrentValue(std::move(aOther.mCurrentValue)) { + aOther.mTag = nullptr; + FindSlot(mTag)->set(&mCurrentValue); +} + +/* static */ ScopedLogExtraInfo::ScopedLogExtraInfoMap +ScopedLogExtraInfo::GetExtraInfoMap() { + // This could be done in a cheaper way, but this is never called on a hot + // path, so we anticipate using a real map inside here to make use simpler for + // the caller(s). + + ScopedLogExtraInfoMap map; + if (XRE_IsParentProcess()) { + if (sQueryValue.get()) { + map.emplace(kTagQuery, sQueryValue.get()); + } + + if (sContextValue.get()) { + map.emplace(kTagContext, sContextValue.get()); + } + } + return map; +} + +/* static */ void ScopedLogExtraInfo::Initialize() { + MOZ_ALWAYS_TRUE(sQueryValue.init()); + MOZ_ALWAYS_TRUE(sContextValue.init()); +} + +void ScopedLogExtraInfo::AddInfo() { + auto* slot = FindSlot(mTag); + MOZ_ASSERT(slot); + mPreviousValue = slot->get(); + + slot->set(&mCurrentValue); +} +#endif + +void LogError(const nsLiteralCString& aModule, const nsACString& aExpr, + const nsACString& aSourceFile, int32_t aSourceLine, + Maybe<nsresult> aRv) { + nsAutoCString extraInfosString; + + nsAutoCString rvName; + if (aRv) { + if (NS_ERROR_GET_MODULE(*aRv) == NS_ERROR_MODULE_WIN32) { + // XXX We could also try to get the Win32 error name here. + rvName = nsPrintfCString("WIN32(0x%" PRIX16 ")", NS_ERROR_GET_CODE(*aRv)); + } else { + rvName = mozilla::GetStaticErrorName(*aRv); + } + extraInfosString.AppendPrintf( + " failed with " + "result 0x%" PRIX32 "%s%s%s", + static_cast<uint32_t>(*aRv), !rvName.IsEmpty() ? " (" : "", + !rvName.IsEmpty() ? rvName.get() : "", !rvName.IsEmpty() ? ")" : ""); + } + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfos = ScopedLogExtraInfo::GetExtraInfoMap(); + for (const auto& item : extraInfos) { + extraInfosString.Append(", "_ns + nsDependentCString(item.first) + "="_ns + + *item.second); + } +#endif + +#ifdef DEBUG + NS_DebugBreak(NS_DEBUG_WARNING, nsAutoCString(aModule + " failure"_ns).get(), + (extraInfosString.IsEmpty() + ? nsPromiseFlatCString(aExpr) + : static_cast<const nsCString&>( + nsAutoCString(aExpr + extraInfosString))) + .get(), + nsPromiseFlatCString(aSourceFile).get(), aSourceLine); +#endif + +#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG) + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (console) { + NS_ConvertUTF8toUTF16 message(aModule + " failure: '"_ns + aExpr + + "', file "_ns + GetLeafName(aSourceFile) + + ", line "_ns + IntToCString(aSourceLine) + + extraInfosString); + + // The concatenation above results in a message like: + // QuotaManager failure: 'EXP', file XYZ, line N) + + console->LogStringMessage(message.get()); + } + +# ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + if (const auto contextIt = extraInfos.find(ScopedLogExtraInfo::kTagContext); + contextIt != extraInfos.cend()) { + // For now, we don't include aExpr in the telemetry event. It might help to + // match locations across versions, but they might be large. + auto extra = Some([&] { + auto res = CopyableTArray<EventExtraEntry>{}; + res.SetCapacity(5); + res.AppendElement(EventExtraEntry{"module"_ns, aModule}); + res.AppendElement(EventExtraEntry{"source_file"_ns, + nsCString(GetLeafName(aSourceFile))}); + res.AppendElement( + EventExtraEntry{"source_line"_ns, IntToCString(aSourceLine)}); + res.AppendElement(EventExtraEntry{ + "context"_ns, nsPromiseFlatCString{*contextIt->second}}); + + if (!rvName.IsEmpty()) { + res.AppendElement(EventExtraEntry{"result"_ns, nsCString{rvName}}); + } + + static Atomic<int32_t> sSequenceNumber{0}; + + res.AppendElement( + EventExtraEntry{"seq"_ns, IntToCString(++sSequenceNumber)}); + + return res; + }()); + + Telemetry::RecordEvent(Telemetry::EventID::DomQuotaTry_Error_Step, + Nothing(), extra); + } +# endif +#endif +} + +#ifdef DEBUG +Result<bool, nsresult> WarnIfFileIsUnknown(nsIFile& aFile, + const char* aSourceFile, + const int32_t aSourceLine) { + nsString leafName; + nsresult rv = aFile.GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + bool isDirectory; + rv = aFile.IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + if (!isDirectory) { + // Don't warn about OS metadata files. These files are only used in + // different platforms, but the profile can be shared across different + // operating systems, so we check it on all platforms. + if (leafName.Equals(kDSStoreFileName) || + leafName.Equals(kDesktopFileName) || + leafName.Equals(kDesktopIniFileName, + nsCaseInsensitiveStringComparator) || + leafName.Equals(kThumbsDbFileName, nsCaseInsensitiveStringComparator)) { + return false; + } + + // Don't warn about files starting with ".". + if (leafName.First() == char16_t('.')) { + return false; + } + } + + NS_DebugBreak( + NS_DEBUG_WARNING, + nsPrintfCString("Something (%s) in the directory that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()) + .get(), + nullptr, aSourceFile, aSourceLine); + + return true; +} +#endif + +} // namespace mozilla::dom::quota |