1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "QuotaCommon.h"
9 #include "mozIStorageConnection.h"
10 #include "mozIStorageStatement.h"
11 #include "mozilla/ErrorNames.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Telemetry.h"
14 #include "mozilla/TelemetryComms.h"
15 #include "mozilla/TelemetryEventEnums.h"
16 #include "mozilla/TextUtils.h"
17 #include "nsIConsoleService.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsStringFlags.h"
21 #include "nsTStringRepr.h"
22 #include "nsUnicharUtils.h"
24 #include "nsXULAppAPI.h"
27 # include "mozilla/Atomics.h"
28 # include "mozilla/ipc/BackgroundParent.h"
29 # include "mozilla/StaticPrefs_dom.h"
30 # include "nsILocalFileWin.h"
33 namespace mozilla::dom::quota
{
35 using namespace mozilla::Telemetry
;
40 constexpr auto kDSStoreFileName
= u
".DS_Store"_ns
;
41 constexpr auto kDesktopFileName
= u
".desktop"_ns
;
42 constexpr auto kDesktopIniFileName
= u
"desktop.ini"_ns
;
43 constexpr auto kThumbsDbFileName
= u
"thumbs.db"_ns
;
47 Atomic
<int32_t> gUseDOSDevicePathSyntax(-1);
50 LazyLogModule
gLogger("QuotaManager");
52 void AnonymizeCString(nsACString
& aCString
, uint32_t aStart
) {
53 MOZ_ASSERT(!aCString
.IsEmpty());
54 MOZ_ASSERT(aStart
< aCString
.Length());
56 char* iter
= aCString
.BeginWriting() + aStart
;
57 char* end
= aCString
.EndWriting();
62 if (IsAsciiAlpha(c
)) {
64 } else if (IsAsciiDigit(c
)) {
74 const char kQuotaGenericDelimiter
= '|';
77 const nsLiteralCString kQuotaInternalError
= "internal"_ns
;
78 const nsLiteralCString kQuotaExternalError
= "external"_ns
;
81 LogModule
* GetQuotaManagerLogger() { return gLogger
; }
83 void AnonymizeCString(nsACString
& aCString
) {
84 if (aCString
.IsEmpty()) {
87 AnonymizeCString(aCString
, /* aStart */ 0);
90 void AnonymizeOriginString(nsACString
& aOriginString
) {
91 if (aOriginString
.IsEmpty()) {
95 int32_t start
= aOriginString
.FindChar(':');
100 AnonymizeCString(aOriginString
, start
);
104 void CacheUseDOSDevicePathSyntaxPrefValue() {
105 MOZ_ASSERT(XRE_IsParentProcess());
106 AssertIsOnBackgroundThread();
108 if (gUseDOSDevicePathSyntax
== -1) {
109 bool useDOSDevicePathSyntax
=
110 StaticPrefs::dom_quotaManager_useDOSDevicePathSyntax_DoNotUseDirectly();
111 gUseDOSDevicePathSyntax
= useDOSDevicePathSyntax
? 1 : 0;
116 Result
<nsCOMPtr
<nsIFile
>, nsresult
> QM_NewLocalFile(const nsAString
& aPath
) {
117 QM_TRY_UNWRAP(auto file
,
118 ToResultInvoke
<nsCOMPtr
<nsIFile
>>(NS_NewLocalFile
, aPath
,
119 /* aFollowLinks */ false),
120 QM_PROPAGATE
, [&aPath
](const nsresult rv
) {
121 QM_WARNING("Failed to construct a file for path (%s)",
122 NS_ConvertUTF16toUTF8(aPath
).get());
126 MOZ_ASSERT(gUseDOSDevicePathSyntax
!= -1);
128 if (gUseDOSDevicePathSyntax
) {
129 QM_TRY_INSPECT(const auto& winFile
,
130 ToResultGet
<nsCOMPtr
<nsILocalFileWin
>>(
131 MOZ_SELECT_OVERLOAD(do_QueryInterface
), file
));
134 winFile
->SetUseDOSDevicePathSyntax(true);
141 nsDependentCSubstring
GetLeafName(const nsACString
& aPath
) {
142 nsACString::const_iterator start
, end
;
143 aPath
.BeginReading(start
);
144 aPath
.EndReading(end
);
146 bool found
= RFindInReadable("/"_ns
, start
, end
);
151 aPath
.EndReading(end
);
153 return nsDependentCSubstring(start
.get(), end
.get());
156 Result
<nsCOMPtr
<nsIFile
>, nsresult
> CloneFileAndAppend(
157 nsIFile
& aDirectory
, const nsAString
& aPathElement
) {
158 QM_TRY_UNWRAP(auto resultFile
, MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<nsIFile
>,
161 QM_TRY(resultFile
->Append(aPathElement
));
166 Result
<nsIFileKind
, nsresult
> GetDirEntryKind(nsIFile
& aFile
) {
168 MOZ_TO_RESULT_INVOKE(aFile
, IsDirectory
)
169 .map([](const bool isDirectory
) {
170 return isDirectory
? nsIFileKind::ExistsAsDirectory
171 : nsIFileKind::ExistsAsFile
;
173 .orElse([](const nsresult rv
) -> Result
<nsIFileKind
, nsresult
> {
174 if (rv
== NS_ERROR_FILE_NOT_FOUND
||
175 rv
== NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
) {
176 return nsIFileKind::DoesNotExist
;
183 Result
<nsCOMPtr
<mozIStorageStatement
>, nsresult
> CreateStatement(
184 mozIStorageConnection
& aConnection
, const nsACString
& aStatementString
) {
185 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr
<mozIStorageStatement
>,
186 aConnection
, CreateStatement
,
190 template <SingleStepResult ResultHandling
>
191 Result
<SingleStepSuccessType
<ResultHandling
>, nsresult
> ExecuteSingleStep(
192 nsCOMPtr
<mozIStorageStatement
>&& aStatement
) {
193 QM_TRY_INSPECT(const bool& hasResult
,
194 MOZ_TO_RESULT_INVOKE(aStatement
, ExecuteStep
));
196 if constexpr (ResultHandling
== SingleStepResult::AssertHasResult
) {
197 MOZ_ASSERT(hasResult
);
200 return WrapNotNullUnchecked(std::move(aStatement
));
202 return hasResult
? std::move(aStatement
) : nullptr;
206 template Result
<SingleStepSuccessType
<SingleStepResult::AssertHasResult
>,
208 ExecuteSingleStep
<SingleStepResult::AssertHasResult
>(
209 nsCOMPtr
<mozIStorageStatement
>&&);
211 template Result
<SingleStepSuccessType
<SingleStepResult::ReturnNullIfNoResult
>,
213 ExecuteSingleStep
<SingleStepResult::ReturnNullIfNoResult
>(
214 nsCOMPtr
<mozIStorageStatement
>&&);
216 template <SingleStepResult ResultHandling
>
217 Result
<SingleStepSuccessType
<ResultHandling
>, nsresult
>
218 CreateAndExecuteSingleStepStatement(mozIStorageConnection
& aConnection
,
219 const nsACString
& aStatementString
) {
220 QM_TRY_UNWRAP(auto stmt
, MOZ_TO_RESULT_INVOKE_TYPED(
221 nsCOMPtr
<mozIStorageStatement
>, aConnection
,
222 CreateStatement
, aStatementString
));
224 return ExecuteSingleStep
<ResultHandling
>(std::move(stmt
));
227 template Result
<SingleStepSuccessType
<SingleStepResult::AssertHasResult
>,
229 CreateAndExecuteSingleStepStatement
<SingleStepResult::AssertHasResult
>(
230 mozIStorageConnection
& aConnection
, const nsACString
& aStatementString
);
232 template Result
<SingleStepSuccessType
<SingleStepResult::ReturnNullIfNoResult
>,
234 CreateAndExecuteSingleStepStatement
<SingleStepResult::ReturnNullIfNoResult
>(
235 mozIStorageConnection
& aConnection
, const nsACString
& aStatementString
);
237 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
238 MOZ_THREAD_LOCAL(const nsACString
*) ScopedLogExtraInfo::sQueryValue
;
239 MOZ_THREAD_LOCAL(const nsACString
*) ScopedLogExtraInfo::sContextValue
;
242 auto ScopedLogExtraInfo::FindSlot(const char* aTag
) {
243 // XXX For now, don't use a real map but just allow the known tag values.
245 if (aTag
== kTagQuery
) {
249 if (aTag
== kTagContext
) {
250 return &sContextValue
;
253 MOZ_CRASH("Unknown tag!");
256 ScopedLogExtraInfo::~ScopedLogExtraInfo() {
258 MOZ_ASSERT(&mCurrentValue
== FindSlot(mTag
)->get(),
259 "Bad scoping of ScopedLogExtraInfo, must not be interleaved!");
261 FindSlot(mTag
)->set(mPreviousValue
);
265 ScopedLogExtraInfo::ScopedLogExtraInfo(ScopedLogExtraInfo
&& aOther
)
267 mPreviousValue(aOther
.mPreviousValue
),
268 mCurrentValue(std::move(aOther
.mCurrentValue
)) {
269 aOther
.mTag
= nullptr;
270 FindSlot(mTag
)->set(&mCurrentValue
);
273 /* static */ ScopedLogExtraInfo::ScopedLogExtraInfoMap
274 ScopedLogExtraInfo::GetExtraInfoMap() {
275 // This could be done in a cheaper way, but this is never called on a hot
276 // path, so we anticipate using a real map inside here to make use simpler for
279 ScopedLogExtraInfoMap map
;
280 if (XRE_IsParentProcess()) {
281 if (sQueryValue
.get()) {
282 map
.emplace(kTagQuery
, sQueryValue
.get());
285 if (sContextValue
.get()) {
286 map
.emplace(kTagContext
, sContextValue
.get());
292 /* static */ void ScopedLogExtraInfo::Initialize() {
293 MOZ_ALWAYS_TRUE(sQueryValue
.init());
294 MOZ_ALWAYS_TRUE(sContextValue
.init());
297 void ScopedLogExtraInfo::AddInfo() {
298 auto* slot
= FindSlot(mTag
);
300 mPreviousValue
= slot
->get();
302 slot
->set(&mCurrentValue
);
306 void LogError(const nsLiteralCString
& aModule
, const nsACString
& aExpr
,
307 const nsACString
& aSourceFile
, int32_t aSourceLine
,
308 Maybe
<nsresult
> aRv
) {
309 nsAutoCString extraInfosString
;
311 nsAutoCString rvName
;
313 if (NS_ERROR_GET_MODULE(*aRv
) == NS_ERROR_MODULE_WIN32
) {
314 // XXX We could also try to get the Win32 error name here.
315 rvName
= nsPrintfCString("WIN32(0x%" PRIX16
")", NS_ERROR_GET_CODE(*aRv
));
317 rvName
= mozilla::GetStaticErrorName(*aRv
);
319 extraInfosString
.AppendPrintf(
321 "result 0x%" PRIX32
"%s%s%s",
322 static_cast<uint32_t>(*aRv
), !rvName
.IsEmpty() ? " (" : "",
323 !rvName
.IsEmpty() ? rvName
.get() : "", !rvName
.IsEmpty() ? ")" : "");
326 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
327 const auto& extraInfos
= ScopedLogExtraInfo::GetExtraInfoMap();
328 for (const auto& item
: extraInfos
) {
329 extraInfosString
.Append(", "_ns
+ nsDependentCString(item
.first
) + "="_ns
+
335 NS_DebugBreak(NS_DEBUG_WARNING
, nsAutoCString(aModule
+ " failure"_ns
).get(),
336 (extraInfosString
.IsEmpty()
337 ? nsPromiseFlatCString(aExpr
)
338 : static_cast<const nsCString
&>(
339 nsAutoCString(aExpr
+ extraInfosString
)))
341 nsPromiseFlatCString(aSourceFile
).get(), aSourceLine
);
344 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
345 nsCOMPtr
<nsIConsoleService
> console
=
346 do_GetService(NS_CONSOLESERVICE_CONTRACTID
);
348 NS_ConvertUTF8toUTF16
message(aModule
+ " failure: '"_ns
+ aExpr
+
349 "', file "_ns
+ GetLeafName(aSourceFile
) +
350 ", line "_ns
+ IntToCString(aSourceLine
) +
353 // The concatenation above results in a message like:
354 // QuotaManager failure: 'EXP', file XYZ, line N)
356 console
->LogStringMessage(message
.get());
359 # ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
360 if (const auto contextIt
= extraInfos
.find(ScopedLogExtraInfo::kTagContext
);
361 contextIt
!= extraInfos
.cend()) {
362 // For now, we don't include aExpr in the telemetry event. It might help to
363 // match locations across versions, but they might be large.
364 auto extra
= Some([&] {
365 auto res
= CopyableTArray
<EventExtraEntry
>{};
367 res
.AppendElement(EventExtraEntry
{"module"_ns
, aModule
});
368 res
.AppendElement(EventExtraEntry
{"source_file"_ns
,
369 nsCString(GetLeafName(aSourceFile
))});
371 EventExtraEntry
{"source_line"_ns
, IntToCString(aSourceLine
)});
372 res
.AppendElement(EventExtraEntry
{
373 "context"_ns
, nsPromiseFlatCString
{*contextIt
->second
}});
375 if (!rvName
.IsEmpty()) {
376 res
.AppendElement(EventExtraEntry
{"result"_ns
, nsCString
{rvName
}});
379 static Atomic
<int32_t> sSequenceNumber
{0};
382 EventExtraEntry
{"seq"_ns
, IntToCString(++sSequenceNumber
)});
387 Telemetry::RecordEvent(Telemetry::EventID::DomQuotaTry_Error_Step
,
395 Result
<bool, nsresult
> WarnIfFileIsUnknown(nsIFile
& aFile
,
396 const char* aSourceFile
,
397 const int32_t aSourceLine
) {
399 nsresult rv
= aFile
.GetLeafName(leafName
);
400 if (NS_WARN_IF(NS_FAILED(rv
))) {
405 rv
= aFile
.IsDirectory(&isDirectory
);
406 if (NS_WARN_IF(NS_FAILED(rv
))) {
411 // Don't warn about OS metadata files. These files are only used in
412 // different platforms, but the profile can be shared across different
413 // operating systems, so we check it on all platforms.
414 if (leafName
.Equals(kDSStoreFileName
) ||
415 leafName
.Equals(kDesktopFileName
) ||
416 leafName
.Equals(kDesktopIniFileName
,
417 nsCaseInsensitiveStringComparator
) ||
418 leafName
.Equals(kThumbsDbFileName
, nsCaseInsensitiveStringComparator
)) {
422 // Don't warn about files starting with ".".
423 if (leafName
.First() == char16_t('.')) {
430 nsPrintfCString("Something (%s) in the directory that doesn't belong!",
431 NS_ConvertUTF16toUTF8(leafName
).get())
433 nullptr, aSourceFile
, aSourceLine
);
439 } // namespace mozilla::dom::quota