Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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,
#include "ActorsParent.h"
// Local includes
#include "CanonicalQuotaObject.h"
#include "ClientUsageArray.h"
#include "Flatten.h"
#include "FirstInitializationAttemptsImpl.h"
#include "GroupInfo.h"
#include "GroupInfoPair.h"
#include "NormalOriginOperationBase.h"
#include "OriginOperationBase.h"
#include "OriginOperations.h"
#include "OriginParser.h"
#include "OriginScope.h"
#include "OriginInfo.h"
#include "QuotaCommon.h"
#include "QuotaManager.h"
#include "ResolvableNormalOriginOp.h"
#include "SanitizationUtils.h"
#include "ScopedLogExtraInfo.h"
#include "UsageInfo.h"
// Global includes
#include <cinttypes>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <new>
#include <numeric>
#include <tuple>
#include <type_traits>
#include <utility>
#include "DirectoryLockImpl.h"
#include "ErrorList.h"
#include "MainThreadUtils.h"
#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "mozStorageHelper.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/CondVar.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/NotNull.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/SystemPrincipal.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TextUtils.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/dom/FileSystemQuotaClientFactory.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/StorageDBUpdater.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/localstorage/ActorsParent.h"
#include "mozilla/dom/quota/AssertionsImpl.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/Config.h"
#include "mozilla/dom/quota/Constants.h"
#include "mozilla/dom/quota/DirectoryLock.h"
#include "mozilla/dom/quota/DirectoryLockInlines.h"
#include "mozilla/dom/quota/FileUtils.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/dom/quota/ScopedLogExtraInfo.h"
#include "mozilla/dom/quota/StreamUtils.h"
#include "mozilla/dom/simpledb/ActorsParent.h"
#include "mozilla/fallible.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/net/ExtensionProtocolHandler.h"
#include "mozilla/StorageOriginAttributes.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsBaseHashtable.h"
#include "nsCOMPtr.h"
#include "nsCRTGlue.h"
#include "nsClassHashtable.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsDirectoryServiceUtils.h"
#include "nsError.h"
#include "nsIBinaryInputStream.h"
#include "nsIBinaryOutputStream.h"
#include "nsIConsoleService.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIDUtils.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIInputStream.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nsIQuotaRequests.h"
#include "nsIPlatformInfo.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsISupports.h"
#include "nsISupportsPrimitives.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsIWidget.h"
#include "nsLiteralString.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
#include "nsStandardURL.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsTLiteralString.h"
#include "nsTPromiseFlatString.h"
#include "nsTStringRepr.h"
#include "nsThreadUtils.h"
#include "nsURLHelper.h"
#include "nsXPCOM.h"
#include "nsXPCOMCID.h"
#include "nsXULAppAPI.h"
#include "prinrval.h"
#include "prio.h"
#include "prtime.h"
// The amount of time, in milliseconds, that our IO thread will stay alive
// after the last event it processes.
#define DEFAULT_THREAD_TIMEOUT_MS 30000
/**
* If shutdown takes this long, kill actors of a quota client, to avoid reaching
* the crash timeout.
*/
#define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
/**
* Automatically crash the browser if shutdown of a quota client takes this
* long. We've chosen a value that is long enough that it is unlikely for the
* problem to be falsely triggered by slow system I/O. We've also chosen a
* value long enough so that automated tests should time out and fail if
* shutdown of a quota client takes too long. Also, this value is long enough
* so that testers can notice the timeout; we want to know about the timeouts,
* not hide them. On the other hand this value is less than 60 seconds which is
* used by nsTerminator to crash a hung main process.
*/
#define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
static_assert(
SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
"The kill actors timeout must be shorter than the crash browser one.");
// profile-before-change, when we need to shut down quota manager
#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
#define KB *1024ULL
#define MB *1024ULL KB
#define GB *1024ULL MB
namespace mozilla::dom::quota {
using namespace mozilla::ipc;
// We want profiles to be platform-independent so we always need to replace
// the same characters on every platform. Windows has the most extensive set
// of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
// FILE_PATH_SEPARATOR.
const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
const char16_t QuotaManager::kReplaceChars16[] =
u"" CONTROL_CHARACTERS "/:*?\"<>|\\";
namespace {
/*******************************************************************************
* Constants
******************************************************************************/
const uint32_t kSQLitePageSizeOverride = 512;
// Important version history:
// which caused Firefox 57 release concerns because the major schema upgrade
// means anyone downgrading to Firefox 56 will experience a non-operational
// QuotaManager and all of its clients.
// rename 3.0 to 2.1, effective in Firefox 57. This works because post
// storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
// increases. It also works because all the upgrade did was give the DOM
// Cache API QuotaClient an opportunity to create its newly added .padding
// files during initialization/upgrade, which isn't functionally necessary as
// that can be done on demand.
// Major storage version. Bump for backwards-incompatible changes.
// downgrade snafu.)
const uint32_t kMajorStorageVersion = 2;
// Minor storage version. Bump for backwards-compatible changes.
const uint32_t kMinorStorageVersion = 3;
// The storage version we store in the SQLite database is a (signed) 32-bit
// integer. The major version is left-shifted 16 bits so the max value is
// 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
static_assert(kMajorStorageVersion <= 0xFFFF,
"Major version needs to fit in 16 bits.");
static_assert(kMinorStorageVersion <= 0xFFFF,
"Minor version needs to fit in 16 bits.");
const int32_t kStorageVersion =
int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
// See comments above about why these are a thing.
const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
const char kIndexedDBOriginPrefix[] = "indexeddb://";
const char kResourceOriginPrefix[] = "resource://";
constexpr auto kStorageName = u"storage"_ns;
#define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
#define ARCHIVES_DIRECTORY_NAME u"archives"
#define PERSISTENT_DIRECTORY_NAME u"persistent"
#define PERMANENT_DIRECTORY_NAME u"permanent"
#define TEMPORARY_DIRECTORY_NAME u"temporary"
#define DEFAULT_DIRECTORY_NAME u"default"
#define PRIVATE_DIRECTORY_NAME u"private"
#define TOBEREMOVED_DIRECTORY_NAME u"to-be-removed"
#define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
#define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
#define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
const int32_t kLocalStorageArchiveVersion = 4;
const char kProfileDoChangeTopic[] = "profile-do-change";
const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
const int32_t kCacheVersion = 2;
/******************************************************************************
* SQLite functions
******************************************************************************/
int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
uint32_t aMinorStorageVersion) {
return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
}
uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
return uint32_t(aStorageVersion >> 16);
}
nsresult CreateTables(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
// Table `database`
QM_TRY(MOZ_TO_RESULT(
aConnection->ExecuteSimpleSQL("CREATE TABLE database"
"( cache_version INTEGER NOT NULL DEFAULT 0"
");"_ns)));
#ifdef DEBUG
{
QM_TRY_INSPECT(const int32_t& storageVersion,
MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
MOZ_ASSERT(storageVersion == 0);
}
#endif
QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
return NS_OK;
}
Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement<
SingleStepResult::ReturnNullIfNoResult>(
aConnection, "SELECT cache_version FROM database"_ns));
QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
}
nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
int32_t aVersion) {
AssertIsOnIOThread();
QM_TRY_INSPECT(
const auto& stmt,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
"UPDATE database SET cache_version = :version;"_ns));
QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
return NS_OK;
}
nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
// Table `cache`
QM_TRY(MOZ_TO_RESULT(
aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
"( valid INTEGER NOT NULL DEFAULT 0"
", build_id TEXT NOT NULL DEFAULT ''"
");"_ns)));
// Table `repository`
QM_TRY(
MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
"( id INTEGER PRIMARY KEY"
", name TEXT NOT NULL"
");"_ns)));
// Table `origin`
QM_TRY(MOZ_TO_RESULT(
aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
"( repository_id INTEGER NOT NULL"
", suffix TEXT"
", group_ TEXT NOT NULL"
", origin TEXT NOT NULL"
", client_usages TEXT NOT NULL"
", usage INTEGER NOT NULL"
", last_access_time INTEGER NOT NULL"
", accessed INTEGER NOT NULL"
", persisted INTEGER NOT NULL"
", PRIMARY KEY (repository_id, origin)"
", FOREIGN KEY (repository_id) "
"REFERENCES repository(id) "
");"_ns)));
#ifdef DEBUG
{
QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
MOZ_ASSERT(cacheVersion == 0);
}
#endif
QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
return NS_OK;
}
OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
QM_TRY(QM_OR_ELSE_WARN(
// Expression.
([&]() -> OkOrErr {
mozStorageTransaction transaction(&aConnection,
/*aCommitOnComplete */ false);
QM_TRY(QM_TO_RESULT(transaction.Start()));
QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
QM_TRY(
QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
QM_TRY(QM_TO_RESULT(transaction.Commit()));
return Ok{};
}()),
// Fallback.
([&](const QMResult& rv) -> OkOrErr {
QM_TRY(
QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
return Ok{};
})));
return Ok{};
}
nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
"ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
QM_TRY(InvalidateCache(aConnection));
#ifdef DEBUG
{
QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
MOZ_ASSERT(cacheVersion == 1);
}
#endif
QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
return NS_OK;
}
Result<bool, nsresult> MaybeCreateOrUpgradeCache(
mozIStorageConnection& aConnection) {
bool cacheUsable = true;
QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
if (cacheVersion > kCacheVersion) {
cacheUsable = false;
} else if (cacheVersion != kCacheVersion) {
const bool newCache = !cacheVersion;
mozStorageTransaction transaction(
&aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
QM_TRY(MOZ_TO_RESULT(transaction.Start()));
if (newCache) {
QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
#ifdef DEBUG
{
QM_TRY_INSPECT(const int32_t& cacheVersion,
LoadCacheVersion(aConnection));
MOZ_ASSERT(cacheVersion == kCacheVersion);
}
#endif
QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO cache (valid, build_id) "
"VALUES (0, '')"))));
nsCOMPtr<mozIStorageStatement> insertStmt;
for (const PersistenceType persistenceType : kAllPersistenceTypes) {
if (insertStmt) {
MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
} else {
QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageStatement>,
aConnection, CreateStatement,
"INSERT INTO repository (id, name) "
"VALUES (:id, :name)"_ns));
}
QM_TRY(MOZ_TO_RESULT(
insertStmt->BindInt32ByName("id"_ns, persistenceType)));
QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
"name"_ns, PersistenceTypeToString(persistenceType))));
QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
}
} else {
// This logic needs to change next time we change the cache!
static_assert(kCacheVersion == 2,
"Upgrade function needed due to cache version increase.");
while (cacheVersion != kCacheVersion) {
if (cacheVersion == 1) {
QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
} else {
QM_FAIL(Err(NS_ERROR_FAILURE), []() {
QM_WARNING(
"Unable to initialize cache, no upgrade path is "
"available!");
});
}
QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
}
MOZ_ASSERT(cacheVersion == kCacheVersion);
}
QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
}
return cacheUsable;
}
Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
AssertIsOnIOThread();
// Check if the old database exists at all.
QM_TRY_INSPECT(const bool& exists,
MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
if (!exists) {
// webappsstore.sqlite doesn't exist, return a null connection.
return nsCOMPtr<mozIStorageConnection>{};
}
QM_TRY_INSPECT(const bool& isDirectory,
MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
if (isDirectory) {
QM_WARNING("webappsstore.sqlite is not a file!");
return nsCOMPtr<mozIStorageConnection>{};
}
QM_TRY_INSPECT(const auto& connection,
QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageConnection>, aStorageService,
OpenUnsharedDatabase, &aWebAppsStoreFile,
mozIStorageService::CONNECTION_DEFAULT),
// Predicate.
IsDatabaseCorruptionError,
// Fallback. Don't throw an error, leave a corrupted
// webappsstore database as it is.
ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
if (connection) {
// Don't propagate an error, leave a non-updateable webappsstore database as
// it is.
QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
nsCOMPtr<mozIStorageConnection>{});
}
return connection;
}
Result<nsCOMPtr<nsIFile>, QMResult> GetLocalStorageArchiveFile(
const nsAString& aDirectoryPath) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDirectoryPath.IsEmpty());
QM_TRY_UNWRAP(auto lsArchiveFile,
QM_TO_RESULT_TRANSFORM(QM_NewLocalFile(aDirectoryPath)));
QM_TRY(QM_TO_RESULT(
lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
return lsArchiveFile;
}
Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
const nsAString& aDirectoryPath) {
AssertIsOnIOThread();
MOZ_ASSERT(!aDirectoryPath.IsEmpty());
QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
QM_TRY(MOZ_TO_RESULT(
lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
return lsArchiveTmpFile;
}
Result<bool, nsresult> IsLocalStorageArchiveInitialized(
mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
QM_TRY_RETURN(
MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
}
nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
#ifdef DEBUG
{
QM_TRY_INSPECT(const auto& initialized,
IsLocalStorageArchiveInitialized(*aConnection));
MOZ_ASSERT(!initialized);
}
#endif
QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
"CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
QM_TRY_INSPECT(
const auto& stmt,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
"INSERT INTO database (version) VALUES (:version)"_ns));
QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
return NS_OK;
}
Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
QM_TRY_INSPECT(const auto& stmt,
CreateAndExecuteSingleStepStatement<
SingleStepResult::ReturnNullIfNoResult>(
aConnection, "SELECT version FROM database"_ns));
QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
}
nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
int32_t aVersion) {
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = aConnection->CreateStatement(
"UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindInt32ByName("version"_ns, aVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->Execute();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
template <typename FileFunc, typename DirectoryFunc>
Result<mozilla::Ok, nsresult> CollectEachFileEntry(
nsIFile& aDirectory, const FileFunc& aFileFunc,
const DirectoryFunc& aDirectoryFunc) {
AssertIsOnIOThread();
return CollectEachFile(
aDirectory,
[&aFileFunc, &aDirectoryFunc](
const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
switch (dirEntryKind) {
case nsIFileKind::ExistsAsDirectory:
return aDirectoryFunc(file);
case nsIFileKind::ExistsAsFile:
return aFileFunc(file);
case nsIFileKind::DoesNotExist:
// Ignore files that got removed externally while iterating.
break;
}
return Ok{};
});
}
/******************************************************************************
* Quota manager class declarations
******************************************************************************/
} // namespace
class QuotaManager::Observer final : public nsIObserver {
static Observer* sInstance;
bool mPendingProfileChange;
bool mShutdownComplete;
public:
static nsresult Initialize();
static nsIObserver* GetInstance();
static void ShutdownCompleted();
private:
Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
MOZ_ASSERT(NS_IsMainThread());
}
~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
nsresult Init();
nsresult Shutdown();
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
};
namespace {
/*******************************************************************************
* Local class declarations
******************************************************************************/
} // namespace
namespace {
class CollectOriginsHelper final : public Runnable {
uint64_t mMinSizeToBeFreed;
Mutex& mMutex;
CondVar mCondVar;
// The members below are protected by mMutex.
nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
uint64_t mSizeToBeFreed;
bool mWaiting;
public:
CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
// Blocks the current thread until origins are collected on the main thread.
// The returned value contains an aggregate size of those origins.
int64_t BlockAndReturnOriginsForEviction(
nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
private:
~CollectOriginsHelper() = default;
NS_IMETHOD
Run() override;
};
/*******************************************************************************
* Other class declarations
******************************************************************************/
class StoragePressureRunnable final : public Runnable {
const uint64_t mUsage;
public:
explicit StoragePressureRunnable(uint64_t aUsage)
: Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
mUsage(aUsage) {}
private:
~StoragePressureRunnable() = default;
NS_DECL_NSIRUNNABLE
};
class RecordTimeDeltaHelper final : public Runnable {
const Telemetry::HistogramID mHistogram;
// TimeStamps that are set on the IO thread.
LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
// A TimeStamp that is set on the main thread.
LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
public:
explicit RecordTimeDeltaHelper(const Telemetry::HistogramID aHistogram)
: Runnable("dom::quota::RecordTimeDeltaHelper"), mHistogram(aHistogram) {}
TimeStamp Start();
TimeStamp End();
private:
~RecordTimeDeltaHelper() = default;
NS_DECL_NSIRUNNABLE
};
/*******************************************************************************
* Helper classes
******************************************************************************/
/*******************************************************************************
* Helper Functions
******************************************************************************/
// Return whether the group was actually updated.
Result<bool, nsresult> MaybeUpdateGroupForOrigin(
OriginMetadata& aOriginMetadata) {
MOZ_ASSERT(!NS_IsMainThread());
bool updated = false;
if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
updated = true;
}
} else {
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
QM_TRY(MOZ_TO_RESULT(principal));
QM_TRY_INSPECT(const auto& baseDomain,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, principal,
GetBaseDomain));
const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
if (aOriginMetadata.mGroup != upToDateGroup) {
aOriginMetadata.mGroup = upToDateGroup;
updated = true;
}
}
return updated;
}
Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
FullOriginMetadata& aFullOriginMetadata) {
MOZ_ASSERT(!NS_IsMainThread());
if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
QM_TRY_INSPECT(const auto& metadataFile,
quotaManager->GetOriginDirectory(aFullOriginMetadata));
QM_TRY(MOZ_TO_RESULT(
metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
metadataFile, GetLastModifiedTime));
// Need to convert from milliseconds to microseconds.
MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
timestamp *= int64_t(PR_USEC_PER_MSEC);
aFullOriginMetadata.mLastAccessTime = timestamp;
return true;
}
return false;
}
} // namespace
void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
// Get leaf of file path
for (const char* p = aFile; *p; ++p) {
if (*p == '/' && *(p + 1)) {
aFile = p + 1;
}
}
nsContentUtils::LogSimpleConsoleError(
NS_ConvertUTF8toUTF16(
nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
"quota"_ns,
false /* Quota Manager is not active in private browsing mode */,
true /* Quota Manager runs always in a chrome context */);
}
namespace {
bool gInvalidateQuotaCache = false;
StaticAutoPtr<nsString> gBasePath;
StaticAutoPtr<nsString> gStorageName;
StaticAutoPtr<nsCString> gBuildId;
#ifdef DEBUG
bool gQuotaManagerInitialized = false;
#endif
StaticRefPtr<QuotaManager> gInstance;
mozilla::Atomic<bool> gShutdown(false);
// A time stamp that can only be accessed on the main thread.
TimeStamp gLastOSWake;
// XXX Move to QuotaManager once NormalOriginOperationBase is declared in a
// separate and includable file.
using NormalOriginOpArray =
nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
class StorageOperationBase {
protected:
struct OriginProps {
enum Type { eChrome, eContent, eObsolete, eInvalid };
NotNull<nsCOMPtr<nsIFile>> mDirectory;
nsString mLeafName;
nsCString mSpec;
OriginAttributes mAttrs;
int64_t mTimestamp;
OriginMetadata mOriginMetadata;
nsCString mOriginalSuffix;
LazyInitializedOnceEarlyDestructible<const PersistenceType>
mPersistenceType;
Type mType;
bool mNeedsRestore;
bool mNeedsRestore2;
bool mIgnore;
public:
explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
: mDirectory(std::move(aDirectory)),
mTimestamp(0),
mType(eContent),
mNeedsRestore(false),
mNeedsRestore2(false),
mIgnore(false) {}
template <typename PersistenceTypeFunc>
nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
};
nsTArray<OriginProps> mOriginProps;
nsCOMPtr<nsIFile> mDirectory;
public:
explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
AssertIsOnIOThread();
}
NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
protected:
virtual ~StorageOperationBase() = default;
nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
nsACString& aGroup, nsACString& aOrigin,
Nullable<bool>& aIsApp);
// Upgrade helper to load the contents of ".metadata-v2" files from previous
// schema versions. Although QuotaManager has a similar GetDirectoryMetadata2
// method, it is only intended to read current version ".metadata-v2" files.
// And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
// because our "storage.sqlite" lets us track the overall version of the
// storage directory.
nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
nsACString& aSuffix, nsACString& aGroup,
nsACString& aOrigin, bool& aIsApp);
int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
/**
* Rename the origin if the origin string generation from nsIPrincipal
* changed. This consists of renaming the origin in the metadata files and
* renaming the origin directory itself. For simplicity, the origin in
* metadata files is not actually updated, but the metadata files are
* recreated instead.
*
* @param aOriginProps the properties of the origin to check.
*
* @return whether origin was renamed.
*/
Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
nsresult ProcessOriginDirectories();
virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
};
class RepositoryOperationBase : public StorageOperationBase {
public:
explicit RepositoryOperationBase(nsIFile* aDirectory)
: StorageOperationBase(aDirectory) {}
nsresult ProcessRepository();
protected:
virtual ~RepositoryOperationBase() = default;
template <typename UpgradeMethod>
nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
UpgradeMethod aMethod);
private:
virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
bool* aRemoved) = 0;
virtual nsresult PrepareClientDirectory(nsIFile* aFile,
const nsAString& aLeafName,
bool& aRemoved);
};
class CreateOrUpgradeDirectoryMetadataHelper final
: public RepositoryOperationBase {
nsCOMPtr<nsIFile> mPermanentStorageDir;
// The legacy PersistenceType, before the default repository introduction.
enum class LegacyPersistenceType {
Persistent = 0,
Temporary
// The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
// it here.
};
LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;