Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/storage/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 98 kB image not shown  

Quelle  mozStorageConnection.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
 * 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 "BaseVFS.h"
#include "ErrorList.h"
#include "nsError.h"
#include "nsThreadUtils.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIXPConnect.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Mutex.h"
#include "mozilla/CondVar.h"
#include "mozilla/Attributes.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_storage.h"

#include "mozIStorageCompletionCallback.h"
#include "mozIStorageFunction.h"

#include "mozStorageAsyncStatementExecution.h"
#include "mozStorageSQLFunctions.h"
#include "mozStorageConnection.h"
#include "mozStorageService.h"
#include "mozStorageStatement.h"
#include "mozStorageAsyncStatement.h"
#include "mozStorageArgValueArray.h"
#include "mozStoragePrivateHelpers.h"
#include "mozStorageStatementData.h"
#include "ObfuscatingVFS.h"
#include "QuotaVFS.h"
#include "StorageBaseStatementInternal.h"
#include "SQLCollations.h"
#include "FileSystemModule.h"
#include "mozStorageHelper.h"
#include "sqlite3_static_ext.h"

#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Printf.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/RefPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsProxyRelease.h"
#include "nsStringFwd.h"
#include "nsURLHelper.h"

#define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000  // 500 MiB

// Maximum size of the pages cache per connection.
#define MAX_CACHE_SIZE_KIBIBYTES 2048  // 2 MiB

mozilla::LazyLogModule gStorageLog("mozStorage");

// Checks that the protected code is running on the main-thread only if the
// connection was also opened on it.
#ifdef DEBUG
#  define CHECK_MAINTHREAD_ABUSE()                                   \
    do {                                                             \
      NS_WARNING_ASSERTION(                                          \
          eventTargetOpenedOn == GetMainThreadSerialEventTarget() || \
              !NS_IsMainThread(),                                    \
          "Using Storage synchronous API on main-thread, but "       \
          "the connection was opened on another thread.");           \
    } while (0)
#else
#  define CHECK_MAINTHREAD_ABUSE() \
    do { /* Nothing */             \
    } while (0)
#endif

namespace mozilla::storage {

using mozilla::dom::quota::QuotaObject;
using mozilla::Telemetry::AccumulateCategoricalKeyed;
using mozilla::Telemetry::LABELS_SQLITE_STORE_OPEN;
using mozilla::Telemetry::LABELS_SQLITE_STORE_QUERY;

namespace {

int nsresultToSQLiteResult(nsresult aXPCOMResultCode) {
  if (NS_SUCCEEDED(aXPCOMResultCode)) {
    return SQLITE_OK;
  }

  switch (aXPCOMResultCode) {
    case NS_ERROR_FILE_CORRUPTED:
      return SQLITE_CORRUPT;
    case NS_ERROR_FILE_ACCESS_DENIED:
      return SQLITE_CANTOPEN;
    case NS_ERROR_STORAGE_BUSY:
      return SQLITE_BUSY;
    case NS_ERROR_FILE_IS_LOCKED:
      return SQLITE_LOCKED;
    case NS_ERROR_FILE_READ_ONLY:
      return SQLITE_READONLY;
    case NS_ERROR_STORAGE_IOERR:
      return SQLITE_IOERR;
    case NS_ERROR_FILE_NO_DEVICE_SPACE:
      return SQLITE_FULL;
    case NS_ERROR_OUT_OF_MEMORY:
      return SQLITE_NOMEM;
    case NS_ERROR_UNEXPECTED:
      return SQLITE_MISUSE;
    case NS_ERROR_ABORT:
      return SQLITE_ABORT;
    case NS_ERROR_STORAGE_CONSTRAINT:
      return SQLITE_CONSTRAINT;
    default:
      return SQLITE_ERROR;
  }

  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!");
}

////////////////////////////////////////////////////////////////////////////////
//// Variant Specialization Functions (variantToSQLiteT)

int sqlite3_T_int(sqlite3_context* aCtx, int aValue) {
  ::sqlite3_result_int(aCtx, aValue);
  return SQLITE_OK;
}

int sqlite3_T_int64(sqlite3_context* aCtx, sqlite3_int64 aValue) {
  ::sqlite3_result_int64(aCtx, aValue);
  return SQLITE_OK;
}

int sqlite3_T_double(sqlite3_context* aCtx, double aValue) {
  ::sqlite3_result_double(aCtx, aValue);
  return SQLITE_OK;
}

int sqlite3_T_text(sqlite3_context* aCtx, const nsCString& aValue) {
  CheckedInt<int32_t> length(aValue.Length());
  if (!length.isValid()) {
    return SQLITE_MISUSE;
  }
  ::sqlite3_result_text(aCtx, aValue.get(), length.value(), SQLITE_TRANSIENT);
  return SQLITE_OK;
}

int sqlite3_T_text16(sqlite3_context* aCtx, const nsString& aValue) {
  CheckedInt<int32_t> n_bytes =
      CheckedInt<int32_t>(aValue.Length()) * sizeof(char16_t);
  if (!n_bytes.isValid()) {
    return SQLITE_MISUSE;
  }
  ::sqlite3_result_text16(aCtx, aValue.get(), n_bytes.value(),
                          SQLITE_TRANSIENT);
  return SQLITE_OK;
}

int sqlite3_T_null(sqlite3_context* aCtx) {
  ::sqlite3_result_null(aCtx);
  return SQLITE_OK;
}

int sqlite3_T_blob(sqlite3_context* aCtx, const void* aData, int aSize) {
  ::sqlite3_result_blob(aCtx, aData, aSize, free);
  return SQLITE_OK;
}

int sqlite3_T_array(sqlite3_context* aCtx, const void* aData, int aSize,
                    int aType) {
  // Not supported for now.
  return SQLITE_MISUSE;
}

#include "variantToSQLiteT_impl.h"

////////////////////////////////////////////////////////////////////////////////
//// Modules

struct Module {
  const char* name;
  int (*registerFunc)(sqlite3*, const char*);
};

Module gModules[] = {{"filesystem", RegisterFileSystemModule}};

////////////////////////////////////////////////////////////////////////////////
//// Local Functions

int tracefunc(unsigned aReason, void* aClosure, void* aP, void* aX) {
  switch (aReason) {
    case SQLITE_TRACE_STMT: {
      // aP is a pointer to the prepared statement.
      sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP);
      // aX is a pointer to a string containing the unexpanded SQL or a comment,
      // starting with "--"" in case of a trigger.
      char* expanded = static_cast<char*>(aX);
      // Simulate what sqlite_trace was doing.
      if (!::strncmp(expanded, "--", 2)) {
        MOZ_LOG(gStorageLog, LogLevel::Debug,
                ("TRACE_STMT on %p: '%s'", aClosure, expanded));
      } else {
        char* sql = ::sqlite3_expanded_sql(stmt);
        MOZ_LOG(gStorageLog, LogLevel::Debug,
                ("TRACE_STMT on %p: '%s'", aClosure, sql));
        ::sqlite3_free(sql);
      }
      break;
    }
    case SQLITE_TRACE_PROFILE: {
      // aX is pointer to a 64bit integer containing nanoseconds it took to
      // execute the last command.
      sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000;
      if (time > 0) {
        MOZ_LOG(gStorageLog, LogLevel::Debug,
                ("TRACE_TIME on %p: %lldms", aClosure, time));
      }
      break;
    }
  }
  return 0;
}

void basicFunctionHelper(sqlite3_context* aCtx, int aArgc,
                         sqlite3_value** aArgv) {
  void* userData = ::sqlite3_user_data(aCtx);

  mozIStorageFunction* func = static_cast<mozIStorageFunction*>(userData);

  RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
  if (!arguments) return;

  nsCOMPtr<nsIVariant> result;
  nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result));
  if (NS_FAILED(rv)) {
    nsAutoCString errorMessage;
    GetErrorName(rv, errorMessage);
    errorMessage.InsertLiteral("User function returned ", 0);
    errorMessage.Append('!');

    NS_WARNING(errorMessage.get());

    ::sqlite3_result_error(aCtx, errorMessage.get(), -1);
    ::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv));
    return;
  }
  int retcode = variantToSQLiteT(aCtx, result);
  if (retcode != SQLITE_OK) {
    NS_WARNING("User function returned invalid data type!");
    ::sqlite3_result_error(aCtx, "User function returned invalid data type",
                           -1);
  }
}

RefPtr<QuotaObject> GetQuotaObject(sqlite3_file* aFile, bool obfuscatingVFS) {
  return obfuscatingVFS
             ? mozilla::storage::obfsvfs::GetQuotaObjectForFile(aFile)
             : mozilla::storage::quotavfs::GetQuotaObjectForFile(aFile);
}

/**
 * This code is heavily based on the sample at:
 *   http://www.sqlite.org/unlock_notify.html
 */

class UnlockNotification {
 public:
  UnlockNotification()
      : mMutex("UnlockNotification mMutex"),
        mCondVar(mMutex, "UnlockNotification condVar"),
        mSignaled(false) {}

  void Wait() {
    MutexAutoLock lock(mMutex);
    while (!mSignaled) {
      (void)mCondVar.Wait();
    }
  }

  void Signal() {
    MutexAutoLock lock(mMutex);
    mSignaled = true;
    (void)mCondVar.Notify();
  }

 private:
  Mutex mMutex MOZ_UNANNOTATED;
  CondVar mCondVar;
  bool mSignaled;
};

void UnlockNotifyCallback(void** aArgs, int aArgsSize) {
  for (int i = 0; i < aArgsSize; i++) {
    UnlockNotification* notification =
        static_cast<UnlockNotification*>(aArgs[i]);
    notification->Signal();
  }
}

int WaitForUnlockNotify(sqlite3* aDatabase) {
  UnlockNotification notification;
  int srv =
      ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback, ¬ification);
  MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK);
  if (srv == SQLITE_OK) {
    notification.Wait();
  }

  return srv;
}

////////////////////////////////////////////////////////////////////////////////
//// Local Classes

class AsyncCloseConnection final : public Runnable {
 public:
  AsyncCloseConnection(Connection* aConnection, sqlite3* aNativeConnection,
                       nsIRunnable* aCallbackEvent)
      : Runnable("storage::AsyncCloseConnection"),
        mConnection(aConnection),
        mNativeConnection(aNativeConnection),
        mCallbackEvent(aCallbackEvent) {}

  NS_IMETHOD Run() override {
    // Make sure we don't dispatch to the current thread.
    MOZ_ASSERT(!IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn));

    nsCOMPtr<nsIRunnable> event =
        NewRunnableMethod("storage::Connection::shutdownAsyncThread",
                          mConnection, &Connection::shutdownAsyncThread);
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event));

    // Internal close.
    (void)mConnection->internalClose(mNativeConnection);

    // Callback
    if (mCallbackEvent) {
      nsCOMPtr<nsIThread> thread;
      (void)NS_GetMainThread(getter_AddRefs(thread));
      (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
    }

    return NS_OK;
  }

  ~AsyncCloseConnection() override {
    NS_ReleaseOnMainThread("AsyncCloseConnection::mConnection",
                           mConnection.forget());
    NS_ReleaseOnMainThread("AsyncCloseConnection::mCallbackEvent",
                           mCallbackEvent.forget());
  }

 private:
  RefPtr<Connection> mConnection;
  sqlite3* mNativeConnection;
  nsCOMPtr<nsIRunnable> mCallbackEvent;
};

/**
 * An event used to initialize the clone of a connection.
 *
 * Must be executed on the clone's async execution thread.
 */

class AsyncInitializeClone final : public Runnable {
 public:
  /**
   * @param aConnection The connection being cloned.
   * @param aClone The clone.
   * @param aReadOnly If |true|, the clone is read only.
   * @param aCallback A callback to trigger once initialization
   *                  is complete. This event will be called on
   *                  aClone->eventTargetOpenedOn.
   */

  AsyncInitializeClone(Connection* aConnection, Connection* aClone,
                       const bool aReadOnly,
                       mozIStorageCompletionCallback* aCallback)
      : Runnable("storage::AsyncInitializeClone"),
        mConnection(aConnection),
        mClone(aClone),
        mReadOnly(aReadOnly),
        mCallback(aCallback) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread());
    nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
    if (NS_FAILED(rv)) {
      return Dispatch(rv, nullptr);
    }
    return Dispatch(NS_OK,
                    NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone));
  }

 private:
  nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
    RefPtr<CallbackComplete> event =
        new CallbackComplete(aResult, aValue, mCallback.forget());
    return mClone->eventTargetOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
  }

  ~AsyncInitializeClone() override {
    nsCOMPtr<nsIThread> thread;
    DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // Handle ambiguous nsISupports inheritance.
    NS_ProxyRelease("AsyncInitializeClone::mConnection", thread,
                    mConnection.forget());
    NS_ProxyRelease("AsyncInitializeClone::mClone", thread, mClone.forget());

    // Generally, the callback will be released by CallbackComplete.
    // However, if for some reason Run() is not executed, we still
    // need to ensure that it is released here.
    NS_ProxyRelease("AsyncInitializeClone::mCallback", thread,
                    mCallback.forget());
  }

  RefPtr<Connection> mConnection;
  RefPtr<Connection> mClone;
  const bool mReadOnly;
  nsCOMPtr<mozIStorageCompletionCallback> mCallback;
};

/**
 * A listener for async connection closing.
 */

class CloseListener final : public mozIStorageCompletionCallback {
 public:
  NS_DECL_ISUPPORTS
  CloseListener() : mClosed(false) {}

  NS_IMETHOD Complete(nsresult, nsISupports*) override {
    mClosed = true;
    return NS_OK;
  }

  bool mClosed;

 private:
  ~CloseListener() = default;
};

NS_IMPL_ISUPPORTS(CloseListener, mozIStorageCompletionCallback)

class AsyncVacuumEvent final : public Runnable {
 public:
  AsyncVacuumEvent(Connection* aConnection,
                   mozIStorageCompletionCallback* aCallback,
                   bool aUseIncremental, int32_t aSetPageSize)
      : Runnable("storage::AsyncVacuum"),
        mConnection(aConnection),
        mCallback(aCallback),
        mUseIncremental(aUseIncremental),
        mSetPageSize(aSetPageSize),
        mStatus(NS_ERROR_UNEXPECTED) {}

  NS_IMETHOD Run() override {
    // This is initially dispatched to the helper thread, then re-dispatched
    // to the opener thread, where it will callback.
    if (IsOnCurrentSerialEventTarget(mConnection->eventTargetOpenedOn)) {
      // Send the completion event.
      if (mCallback) {
        mozilla::Unused << mCallback->Complete(mStatus, nullptr);
      }
      return NS_OK;
    }

    // Ensure to invoke the callback regardless of errors.
    auto guard = MakeScopeExit([&]() {
      mConnection->mIsStatementOnHelperThreadInterruptible = false;
      mozilla::Unused << mConnection->eventTargetOpenedOn->Dispatch(
          this, NS_DISPATCH_NORMAL);
    });

    // Get list of attached databases.
    nsCOMPtr<mozIStorageStatement> stmt;
    nsresult rv = mConnection->CreateStatement(MOZ_STORAGE_UNIQUIFY_QUERY_STR
                                               "PRAGMA database_list"_ns,
                                               getter_AddRefs(stmt));
    NS_ENSURE_SUCCESS(rv, rv);
    // We must accumulate names and loop through them later, otherwise VACUUM
    // will see an ongoing statement and bail out.
    nsTArray<nsCString> schemaNames;
    bool hasResult = false;
    while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
      nsAutoCString name;
      rv = stmt->GetUTF8String(1, name);
      if (NS_SUCCEEDED(rv) && !name.EqualsLiteral("temp")) {
        schemaNames.AppendElement(name);
      }
    }
    mStatus = NS_OK;
    // Mark this vacuum as an interruptible operation, so it can be interrupted
    // if the connection closes during shutdown.
    mConnection->mIsStatementOnHelperThreadInterruptible = true;
    for (const nsCString& schemaName : schemaNames) {
      rv = this->Vacuum(schemaName);
      if (NS_FAILED(rv)) {
        // This is sub-optimal since it's only keeping the last error reason,
        // but it will do for now.
        mStatus = rv;
      }
    }
    return mStatus;
  }

  nsresult Vacuum(const nsACString& aSchemaName) {
    // Abort if we're in shutdown.
    if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
      return NS_ERROR_ABORT;
    }
    int32_t removablePages = mConnection->RemovablePagesInFreeList(aSchemaName);
    if (!removablePages) {
      // There's no empty pages to remove, so skip this vacuum for now.
      return NS_OK;
    }
    nsresult rv;
    bool needsFullVacuum = true;

    if (mSetPageSize) {
      nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
      query.Append(aSchemaName);
      query.AppendLiteral(".page_size = ");
      query.AppendInt(mSetPageSize);
      nsCOMPtr<mozIStorageStatement> stmt;
      rv = mConnection->ExecuteSimpleSQL(query);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // Check auto_vacuum.
    {
      nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
      query.Append(aSchemaName);
      query.AppendLiteral(".auto_vacuum");
      nsCOMPtr<mozIStorageStatement> stmt;
      rv = mConnection->CreateStatement(query, getter_AddRefs(stmt));
      NS_ENSURE_SUCCESS(rv, rv);
      bool hasResult = false;
      bool changeAutoVacuum = false;
      if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
        bool isIncrementalVacuum = stmt->AsInt32(0) == 2;
        changeAutoVacuum = isIncrementalVacuum != mUseIncremental;
        if (isIncrementalVacuum && !changeAutoVacuum) {
          needsFullVacuum = false;
        }
      }
      // Changing auto_vacuum is only supported on the main schema.
      if (aSchemaName.EqualsLiteral("main") && changeAutoVacuum) {
        nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
        query.Append(aSchemaName);
        query.AppendLiteral(".auto_vacuum = ");
        query.AppendInt(mUseIncremental ? 2 : 0);
        rv = mConnection->ExecuteSimpleSQL(query);
        NS_ENSURE_SUCCESS(rv, rv);
      }
    }

    if (needsFullVacuum) {
      nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "VACUUM ");
      query.Append(aSchemaName);
      rv = mConnection->ExecuteSimpleSQL(query);
      // TODO (Bug 1818039): Report failed vacuum telemetry.
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA ");
      query.Append(aSchemaName);
      query.AppendLiteral(".incremental_vacuum(");
      query.AppendInt(removablePages);
      query.AppendLiteral(")");
      rv = mConnection->ExecuteSimpleSQL(query);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    return NS_OK;
  }

  ~AsyncVacuumEvent() override {
    NS_ReleaseOnMainThread("AsyncVacuum::mConnection", mConnection.forget());
    NS_ReleaseOnMainThread("AsyncVacuum::mCallback", mCallback.forget());
  }

 private:
  RefPtr<Connection> mConnection;
  nsCOMPtr<mozIStorageCompletionCallback> mCallback;
  bool mUseIncremental;
  int32_t mSetPageSize;
  Atomic<nsresult> mStatus;
};

/**
 * A runnable to perform an SQLite database backup when there may be one or more
 * open connections on that database.
 */

class AsyncBackupDatabaseFile final : public Runnable, public nsITimerCallback {
 public:
  NS_DECL_ISUPPORTS_INHERITED

  /**
   * @param aConnection The connection to the database being backed up.
   * @param aNativeConnection The native connection to the database being backed
   *                          up.
   * @param aDestinationFile The destination file for the created backup.
   * @param aCallback A callback to trigger once the backup process has
   *                  completed. The callback will be supplied with an nsresult
   *                  indicating whether or not the backup was successfully
   *                  created. This callback will be called on the
   *                  mConnection->eventTargetOpenedOn thread.
   * @throws
   */

  AsyncBackupDatabaseFile(Connection* aConnection, sqlite3* aNativeConnection,
                          nsIFile* aDestinationFile,
                          mozIStorageCompletionCallback* aCallback,
                          int32_t aPagesPerStep, uint32_t aStepDelayMs)
      : Runnable("storage::AsyncBackupDatabaseFile"),
        mConnection(aConnection),
        mNativeConnection(aNativeConnection),
        mDestinationFile(aDestinationFile),
        mCallback(aCallback),
        mPagesPerStep(aPagesPerStep),
        mStepDelayMs(aStepDelayMs),
        mBackupFile(nullptr),
        mBackupHandle(nullptr) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread());

    nsAutoString path;
    nsresult rv = mDestinationFile->GetPath(path);
    if (NS_FAILED(rv)) {
      return Dispatch(rv, nullptr);
    }
    // Put a .tmp on the end of the destination while the backup is underway.
    // This extension will be stripped off after the backup successfully
    // completes.
    path.AppendLiteral(".tmp");

    int srv = ::sqlite3_open(NS_ConvertUTF16toUTF8(path).get(), &mBackupFile);
    if (srv != SQLITE_OK) {
      ::sqlite3_close(mBackupFile);
      mBackupFile = nullptr;
      return Dispatch(NS_ERROR_FAILURE, nullptr);
    }

    static const char* mainDBName = "main";

    mBackupHandle = ::sqlite3_backup_init(mBackupFile, mainDBName,
                                          mNativeConnection, mainDBName);
    if (!mBackupHandle) {
      MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK);
      return Dispatch(NS_ERROR_FAILURE, nullptr);
    }

    return DoStep();
  }

  NS_IMETHOD
  Notify(nsITimer* aTimer) override { return DoStep(); }

 private:
  nsresult DoStep() {
#define DISPATCH_AND_RETURN_IF_FAILED(rv) \
  if (NS_FAILED(rv)) {                    \
    return Dispatch(rv, nullptr);         \
  }

    // This guard is used to close the backup database in the event of
    // some failure throughout this process. We release the exit guard
    // only if we complete the backup successfully, or defer to another
    // later call to DoStep.
    auto guard = MakeScopeExit([&]() {
      MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK);
      mBackupFile = nullptr;
    });

    MOZ_ASSERT(!NS_IsMainThread());
    nsAutoString originalPath;
    nsresult rv = mDestinationFile->GetPath(originalPath);
    DISPATCH_AND_RETURN_IF_FAILED(rv);

    nsAutoString tempPath = originalPath;
    tempPath.AppendLiteral(".tmp");

    nsCOMPtr<nsIFile> file;
    rv = NS_NewLocalFile(tempPath, getter_AddRefs(file));
    DISPATCH_AND_RETURN_IF_FAILED(rv);

    int srv = ::sqlite3_backup_step(mBackupHandle, mPagesPerStep);
    if (srv == SQLITE_OK || srv == SQLITE_BUSY || srv == SQLITE_LOCKED) {
      // We're continuing the backup later. Release the guard to avoid closing
      // the database.
      guard.release();
      // Queue up the next step
      return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mStepDelayMs,
                                     nsITimer::TYPE_ONE_SHOT,
                                     GetCurrentSerialEventTarget());
    }
#ifdef DEBUG
    if (srv != SQLITE_DONE) {
      nsCString warnMsg;
      warnMsg.AppendLiteral(
          "The SQLite database copy could not be completed due to an error: ");
      warnMsg.Append(::sqlite3_errmsg(mBackupFile));
      NS_WARNING(warnMsg.get());
    }
#endif

    (void)::sqlite3_backup_finish(mBackupHandle);
    MOZ_ALWAYS_TRUE(::sqlite3_close(mBackupFile) == SQLITE_OK);
    mBackupFile = nullptr;

    // The database is already closed, so we can release this guard now.
    guard.release();

    if (srv != SQLITE_DONE) {
      NS_WARNING("Failed to create database copy.");

      // The partially created database file is not useful. Let's remove it.
      rv = file->Remove(false);
      if (NS_FAILED(rv)) {
        NS_WARNING(
            "Removing a partially backed up SQLite database file failed.");
      }

      return Dispatch(convertResultCode(srv), nullptr);
    }

    // Now that we've successfully created the copy, we'll strip off the .tmp
    // extension.

    nsAutoString leafName;
    rv = mDestinationFile->GetLeafName(leafName);
    DISPATCH_AND_RETURN_IF_FAILED(rv);

    rv = file->RenameTo(nullptr, leafName);
    DISPATCH_AND_RETURN_IF_FAILED(rv);

#undef DISPATCH_AND_RETURN_IF_FAILED
    return Dispatch(NS_OK, nullptr);
  }

  nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
    RefPtr<CallbackComplete> event =
        new CallbackComplete(aResult, aValue, mCallback.forget());
    return mConnection->eventTargetOpenedOn->Dispatch(event,
                                                      NS_DISPATCH_NORMAL);
  }

  ~AsyncBackupDatabaseFile() override {
    nsresult rv;
    nsCOMPtr<nsIThread> thread =
        do_QueryInterface(mConnection->eventTargetOpenedOn, &rv);
    MOZ_ASSERT(NS_SUCCEEDED(rv));

    // Handle ambiguous nsISupports inheritance.
    NS_ProxyRelease("AsyncBackupDatabaseFile::mConnection", thread,
                    mConnection.forget());
    NS_ProxyRelease("AsyncBackupDatabaseFile::mDestinationFile", thread,
                    mDestinationFile.forget());

    // Generally, the callback will be released by CallbackComplete.
    // However, if for some reason Run() is not executed, we still
    // need to ensure that it is released here.
    NS_ProxyRelease("AsyncInitializeClone::mCallback", thread,
                    mCallback.forget());
  }

  RefPtr<Connection> mConnection;
  sqlite3* mNativeConnection;
  nsCOMPtr<nsITimer> mTimer;
  nsCOMPtr<nsIFile> mDestinationFile;
  nsCOMPtr<mozIStorageCompletionCallback> mCallback;
  int32_t mPagesPerStep;
  uint32_t mStepDelayMs;
  sqlite3* mBackupFile;
  sqlite3_backup* mBackupHandle;
};

NS_IMPL_ISUPPORTS_INHERITED(AsyncBackupDatabaseFile, Runnable, nsITimerCallback)

}  // namespace

////////////////////////////////////////////////////////////////////////////////
//// Connection

Connection::Connection(Service* aService, int aFlags,
                       ConnectionOperation aSupportedOperations,
                       const nsCString& aTelemetryFilename, bool aInterruptible,
                       bool aIgnoreLockingMode, bool aOpenNotExclusive)
    : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex"),
      sharedDBMutex("Connection::sharedDBMutex"),
      eventTargetOpenedOn(WrapNotNull(GetCurrentSerialEventTarget())),
      mIsStatementOnHelperThreadInterruptible(false),
      mDBConn(nullptr),
      mDefaultTransactionType(mozIStorageConnection::TRANSACTION_DEFERRED),
      mDestroying(false),
      mProgressHandler(nullptr),
      mStorageService(aService),
      mFlags(aFlags),
      mTransactionNestingLevel(0),
      mSupportedOperations(aSupportedOperations),
      mInterruptible(aSupportedOperations == Connection::ASYNCHRONOUS ||
                     aInterruptible),
      mIgnoreLockingMode(aIgnoreLockingMode),
      mOpenNotExclusive(aOpenNotExclusive),
      mAsyncExecutionThreadShuttingDown(false),
      mConnectionClosed(false),
      mGrowthChunkSize(0) {
  MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
             "Can't ignore locking for a non-readonly connection!");
  mStorageService->registerConnection(this);
  MOZ_ASSERT(!aTelemetryFilename.IsEmpty(),
             "A telemetry filename should have been passed-in.");
  mTelemetryFilename.Assign(aTelemetryFilename);
}

Connection::~Connection() {
  // Failsafe Close() occurs in our custom Release method because of
  // complications related to Close() potentially invoking AsyncClose() which
  // will increment our refcount.
  MOZ_ASSERT(!mAsyncExecutionThread,
             "The async thread has not been shutdown properly!");
}

NS_IMPL_ADDREF(Connection)

NS_INTERFACE_MAP_BEGIN(Connection)
  NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY(mozIStorageConnection)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
NS_INTERFACE_MAP_END

// This is identical to what NS_IMPL_RELEASE provides, but with the
// extra |1 == count| case.
NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void) {
  MOZ_ASSERT(0 != mRefCnt, "dup release");
  nsrefcnt count = --mRefCnt;
  NS_LOG_RELEASE(this, count, "Connection");
  if (1 == count) {
    // If the refcount went to 1, the single reference must be from
    // gService->mConnections (in class |Service|).  And the code calling
    // Release is either:
    // - The "user" code that had created the connection, releasing on any
    //   thread.
    // - One of Service's getConnections() callers had acquired a strong
    //   reference to the Connection that out-lived the last "user" reference,
    //   and now that just got dropped.  Note that this reference could be
    //   getting dropped on the main thread or Connection->eventTargetOpenedOn
    //   (because of the NewRunnableMethod used by minimizeMemory).
    //
    // Either way, we should now perform our failsafe Close() and unregister.
    // However, we only want to do this once, and the reality is that our
    // refcount could go back up above 1 and down again at any time if we are
    // off the main thread and getConnections() gets called on the main thread,
    // so we use an atomic here to do this exactly once.
    if (mDestroying.compareExchange(falsetrue)) {
      // Close the connection, dispatching to the opening event target if we're
      // not on that event target already and that event target is still
      // accepting runnables. We do this because it's possible we're on the main
      // thread because of getConnections(), and we REALLY don't want to
      // transfer I/O to the main thread if we can avoid it.
      if (IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) {
        // This could cause SpinningSynchronousClose() to be invoked and AddRef
        // triggered for AsyncCloseConnection's strong ref if the conn was ever
        // use for async purposes.  (Main-thread only, though.)
        Unused << synchronousClose();
      } else {
        nsCOMPtr<nsIRunnable> event =
            NewRunnableMethod("storage::Connection::synchronousClose"this,
                              &Connection::synchronousClose);
        if (NS_FAILED(eventTargetOpenedOn->Dispatch(event.forget(),
                                                    NS_DISPATCH_NORMAL))) {
          // The event target was dead and so we've just leaked our runnable.
          // This should not happen because our non-main-thread consumers should
          // be explicitly closing their connections, not relying on us to close
          // them for them.  (It's okay to let a statement go out of scope for
          // automatic cleanup, but not a Connection.)
          MOZ_ASSERT(false,
                     "Leaked Connection::synchronousClose(), ownership fail.");
          Unused << synchronousClose();
        }
      }

      // This will drop its strong reference right here, right now.
      mStorageService->unregisterConnection(this);
    }
  } else if (0 == count) {
    mRefCnt = 1; /* stabilize */
#if 0            /* enable this to find non-threadsafe destructors: */
    NS_ASSERT_OWNINGTHREAD(Connection);
#endif
    delete (this);
    return 0;
  }
  return count;
}

int32_t Connection::getSqliteRuntimeStatus(int32_t aStatusOption,
                                           int32_t* aMaxValue) {
  MOZ_ASSERT(connectionReady(), "A connection must exist at this point");
  int curr = 0, max = 0;
  DebugOnly<int> rc =
      ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0);
  MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc)));
  if (aMaxValue) *aMaxValue = max;
  return curr;
}

nsIEventTarget* Connection::getAsyncExecutionTarget() {
  NS_ENSURE_TRUE(IsOnCurrentSerialEventTarget(eventTargetOpenedOn), nullptr);

  // Don't return the asynchronous event target if we are shutting down.
  if (mAsyncExecutionThreadShuttingDown) {
    return nullptr;
  }

  // Create the async event target if there's none yet.
  if (!mAsyncExecutionThread) {
    // Names start with "sqldb:" followed by a recognizable name, like the
    // database file name, or a specially crafted name like "memory".
    // This name will be surfaced on https://crash-stats.mozilla.org, so any
    // sensitive part of the file name (e.g. an URL origin) should be replaced
    // by passing an explicit telemetryName to openDatabaseWithFileURL.
    nsAutoCString name("sqldb:"_ns);
    name.Append(mTelemetryFilename);
    static nsThreadPoolNaming naming;
    nsresult rv = NS_NewNamedThread(naming.GetNextThreadName(name),
                                    getter_AddRefs(mAsyncExecutionThread));
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to create async thread.");
      return nullptr;
    }
  }

  return mAsyncExecutionThread;
}

void Connection::RecordOpenStatus(nsresult rv) {
  nsCString histogramKey = mTelemetryFilename;

  if (histogramKey.IsEmpty()) {
    histogramKey.AssignLiteral("unknown");
  }

  if (NS_SUCCEEDED(rv)) {
    AccumulateCategoricalKeyed(histogramKey, LABELS_SQLITE_STORE_OPEN::success);
    return;
  }

  switch (rv) {
    case NS_ERROR_FILE_CORRUPTED:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_OPEN::corrupt);
      break;
    case NS_ERROR_STORAGE_IOERR:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_OPEN::diskio);
      break;
    case NS_ERROR_FILE_ACCESS_DENIED:
    case NS_ERROR_FILE_IS_LOCKED:
    case NS_ERROR_FILE_READ_ONLY:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_OPEN::access);
      break;
    case NS_ERROR_FILE_NO_DEVICE_SPACE:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_OPEN::diskspace);
      break;
    default:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_OPEN::failure);
  }
}

void Connection::RecordQueryStatus(int srv) {
  nsCString histogramKey = mTelemetryFilename;

  if (histogramKey.IsEmpty()) {
    histogramKey.AssignLiteral("unknown");
  }

  switch (srv) {
    case SQLITE_OK:
    case SQLITE_ROW:
    case SQLITE_DONE:

    // Note that these are returned when we intentionally cancel a statement so
    // they aren't indicating a failure.
    case SQLITE_ABORT:
    case SQLITE_INTERRUPT:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::success);
      break;
    case SQLITE_CORRUPT:
    case SQLITE_NOTADB:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::corrupt);
      break;
    case SQLITE_PERM:
    case SQLITE_CANTOPEN:
    case SQLITE_LOCKED:
    case SQLITE_READONLY:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::access);
      break;
    case SQLITE_IOERR:
    case SQLITE_NOLFS:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::diskio);
      break;
    case SQLITE_FULL:
    case SQLITE_TOOBIG:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::diskspace);
      break;
    case SQLITE_CONSTRAINT:
    case SQLITE_RANGE:
    case SQLITE_MISMATCH:
    case SQLITE_MISUSE:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::misuse);
      break;
    case SQLITE_BUSY:
      AccumulateCategoricalKeyed(histogramKey, LABELS_SQLITE_STORE_QUERY::busy);
      break;
    default:
      AccumulateCategoricalKeyed(histogramKey,
                                 LABELS_SQLITE_STORE_QUERY::failure);
  }
}

nsresult Connection::initialize(const nsACString& aStorageKey,
                                const nsACString& aName) {
  MOZ_ASSERT(aStorageKey.Equals(kMozStorageMemoryStorageKey));
  NS_ASSERTION(!connectionReady(),
               "Initialize called on already opened database!");
  MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
  AUTO_PROFILER_LABEL("Connection::initialize", OTHER);

  mStorageKey = aStorageKey;
  mName = aName;

  // in memory database requested, sqlite uses a magic file name

  const nsAutoCString path =
      mName.IsEmpty() ? nsAutoCString(":memory:"_ns)
                      : "file:"_ns + mName + "?mode=memory&cache=shared"_ns;

  int srv = ::sqlite3_open_v2(path.get(), &mDBConn, mFlags,
                              basevfs::GetVFSName(true));
  if (srv != SQLITE_OK) {
    ::sqlite3_close(mDBConn);
    mDBConn = nullptr;
    nsresult rv = convertResultCode(srv);
    RecordOpenStatus(rv);
    return rv;
  }

#ifdef MOZ_SQLITE_FTS3_TOKENIZER
  srv =
      ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
  MOZ_ASSERT(srv == SQLITE_OK,
             "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled");
#endif

  // Do not set mDatabaseFile or mFileURL here since this is a "memory"
  // database.

  nsresult rv = initializeInternal();
  RecordOpenStatus(rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult Connection::initialize(nsIFile* aDatabaseFile) {
  NS_ASSERTION(aDatabaseFile, "Passed null file!");
  NS_ASSERTION(!connectionReady(),
               "Initialize called on already opened database!");
  AUTO_PROFILER_LABEL("Connection::initialize", OTHER);

  // Do not set mFileURL here since this is database does not have an associated
  // URL.
  mDatabaseFile = aDatabaseFile;

  nsAutoString path;
  nsresult rv = aDatabaseFile->GetPath(path);
  NS_ENSURE_SUCCESS(rv, rv);

  bool exclusive =
      StaticPrefs::storage_sqlite_exclusiveLock_enabled() && !mOpenNotExclusive;
  int srv;
  if (mIgnoreLockingMode) {
    exclusive = false;
    srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
                            "readonly-immutable-nolock");
  } else {
    srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
                            basevfs::GetVFSName(exclusive));
    if (exclusive && (srv == SQLITE_LOCKED || srv == SQLITE_BUSY)) {
      ::sqlite3_close(mDBConn);
      // Retry without trying to get an exclusive lock.
      exclusive = false;
      srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn,
                              mFlags, basevfs::GetVFSName(false));
    }
  }
  if (srv != SQLITE_OK) {
    ::sqlite3_close(mDBConn);
    mDBConn = nullptr;
    rv = convertResultCode(srv);
    RecordOpenStatus(rv);
    return rv;
  }

  rv = initializeInternal();
  if (exclusive &&
      (rv == NS_ERROR_STORAGE_BUSY || rv == NS_ERROR_FILE_IS_LOCKED)) {
    // Usually SQLite will fail to acquire an exclusive lock on opening, but in
    // some cases it may successfully open the database and then lock on the
    // first query execution. When initializeInternal fails it closes the
    // connection, so we can try to restart it in non-exclusive mode.
    srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn, mFlags,
                            basevfs::GetVFSName(false));
    if (srv == SQLITE_OK) {
      rv = initializeInternal();
    } else {
      ::sqlite3_close(mDBConn);
      mDBConn = nullptr;
    }
  }

  RecordOpenStatus(rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult Connection::initialize(nsIFileURL* aFileURL) {
  NS_ASSERTION(aFileURL, "Passed null file URL!");
  NS_ASSERTION(!connectionReady(),
               "Initialize called on already opened database!");
  AUTO_PROFILER_LABEL("Connection::initialize", OTHER);

  nsCOMPtr<nsIFile> databaseFile;
  nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile));
  NS_ENSURE_SUCCESS(rv, rv);

  // Set both mDatabaseFile and mFileURL here.
  mFileURL = aFileURL;
  mDatabaseFile = databaseFile;

  nsAutoCString spec;
  rv = aFileURL->GetSpec(spec);
  NS_ENSURE_SUCCESS(rv, rv);

  // If there is a key specified, we need to use the obfuscating VFS.
  nsAutoCString query;
  rv = aFileURL->GetQuery(query);
  NS_ENSURE_SUCCESS(rv, rv);

  bool hasKey = false;
  bool hasDirectoryLockId = false;

  MOZ_ALWAYS_TRUE(
      URLParams::Parse(query, true,
                       [&hasKey, &hasDirectoryLockId](
                           const nsACString& aName, const nsACString& aValue) {
                         if (aName.EqualsLiteral("key")) {
                           hasKey = true;
                           return true;
                         }
                         if (aName.EqualsLiteral("directoryLockId")) {
                           hasDirectoryLockId = true;
                           return true;
                         }
                         return true;
                       }));

  bool exclusive =
      StaticPrefs::storage_sqlite_exclusiveLock_enabled() && !mOpenNotExclusive;

  const charconst vfs = hasKey               ? obfsvfs::GetVFSName()
                          : hasDirectoryLockId ? quotavfs::GetVFSName()
                                               : basevfs::GetVFSName(exclusive);

  int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, vfs);
  if (srv != SQLITE_OK) {
    ::sqlite3_close(mDBConn);
    mDBConn = nullptr;
    rv = convertResultCode(srv);
    RecordOpenStatus(rv);
    return rv;
  }

  rv = initializeInternal();
  RecordOpenStatus(rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult Connection::initializeInternal() {
  MOZ_ASSERT(mDBConn);
  auto guard = MakeScopeExit([&]() { initializeFailed(); });

  mConnectionClosed = false;

#ifdef MOZ_SQLITE_FTS3_TOKENIZER
  DebugOnly<int> srv2 =
      ::sqlite3_db_config(mDBConn, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
  MOZ_ASSERT(srv2 == SQLITE_OK,
             "SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER should be enabled");
#endif

  // Properly wrap the database handle's mutex.
  sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));

  // SQLite tracing can slow down queries (especially long queries)
  // significantly. Don't trace unless the user is actively monitoring SQLite.
  if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
    ::sqlite3_trace_v2(mDBConn, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE,
                       tracefunc, this);

    MOZ_LOG(
        gStorageLog, LogLevel::Debug,
        ("Opening connection to '%s' (%p)", mTelemetryFilename.get(), this));
  }

  int64_t pageSize = Service::kDefaultPageSize;

  // Set page_size to the preferred default value.  This is effective only if
  // the database has just been created, otherwise, if the database does not
  // use WAL journal mode, a VACUUM operation will updated its page_size.
  nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
                              "PRAGMA page_size = ");
  pageSizeQuery.AppendInt(pageSize);
  int srv = executeSql(mDBConn, pageSizeQuery.get());
  if (srv != SQLITE_OK) {
    return convertResultCode(srv);
  }

  // Setting the cache_size forces the database open, verifying if it is valid
  // or corrupt.  So this is executed regardless it being actually needed.
  // The cache_size is calculated from the actual page_size, to save memory.
  nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
                               "PRAGMA cache_size = ");
  cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES);
  srv = executeSql(mDBConn, cacheSizeQuery.get());
  if (srv != SQLITE_OK) {
    return convertResultCode(srv);
  }

  // Register our built-in SQL functions.
  srv = registerFunctions(mDBConn);
  if (srv != SQLITE_OK) {
    return convertResultCode(srv);
  }

  // Register our built-in SQL collating sequences.
  srv = registerCollations(mDBConn, mStorageService);
  if (srv != SQLITE_OK) {
    return convertResultCode(srv);
  }

  // Set the default synchronous value. Each consumer can switch this
  // accordingly to their needs.
#if defined(ANDROID)
  // Android prefers synchronous = OFF for performance reasons.
  Unused << ExecuteSimpleSQL("PRAGMA synchronous = OFF;"_ns);
#else
  // Normal is the suggested value for WAL journals.
  Unused << ExecuteSimpleSQL("PRAGMA synchronous = NORMAL;"_ns);
#endif

  // Initialization succeeded, we can stop guarding for failures.
  guard.release();
  return NS_OK;
}

nsresult Connection::initializeOnAsyncThread(nsIFile* aStorageFile) {
  MOZ_ASSERT(!IsOnCurrentSerialEventTarget(eventTargetOpenedOn));
  nsresult rv = aStorageFile
                    ? initialize(aStorageFile)
                    : initialize(kMozStorageMemoryStorageKey, VoidCString());
  if (NS_FAILED(rv)) {
    // Shutdown the async thread, since initialization failed.
    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
    mAsyncExecutionThreadShuttingDown = true;
    nsCOMPtr<nsIRunnable> event =
        NewRunnableMethod("Connection::shutdownAsyncThread"this,
                          &Connection::shutdownAsyncThread);
    Unused << NS_DispatchToMainThread(event);
  }
  return rv;
}

void Connection::initializeFailed() {
  {
    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
    mConnectionClosed = true;
  }
  MOZ_ALWAYS_TRUE(::sqlite3_close(mDBConn) == SQLITE_OK);
  mDBConn = nullptr;
  sharedDBMutex.destroy();
}

nsresult Connection::databaseElementExists(
    enum DatabaseElementType aElementType, const nsACString& aElementName,
    bool* _exists) {
  if (!connectionReady()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // When constructing the query, make sure to SELECT the correct db's
  // sqlite_master if the user is prefixing the element with a specific db. ex:
  // sample.test
  nsCString query("SELECT name FROM (SELECT * FROM ");
  nsDependentCSubstring element;
  int32_t ind = aElementName.FindChar('.');
  if (ind == kNotFound) {
    element.Assign(aElementName);
  } else {
    nsDependentCSubstring db(Substring(aElementName, 0, ind + 1));
    element.Assign(Substring(aElementName, ind + 1, aElementName.Length()));
    query.Append(db);
  }
  query.AppendLiteral(
      "sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = "
      "'");

  switch (aElementType) {
    case INDEX:
      query.AppendLiteral("index");
      break;
    case TABLE:
      query.AppendLiteral("table");
      break;
  }
  query.AppendLiteral("' AND name ='");
  query.Append(element);
  query.Append('\'');

  sqlite3_stmt* stmt;
  int srv = prepareStatement(mDBConn, query, &stmt);
  if (srv != SQLITE_OK) {
    RecordQueryStatus(srv);
    return convertResultCode(srv);
  }

  srv = stepStatement(mDBConn, stmt);
  // we just care about the return value from step
  (void)::sqlite3_finalize(stmt);

  RecordQueryStatus(srv);

  if (srv == SQLITE_ROW) {
    *_exists = true;
    return NS_OK;
  }
  if (srv == SQLITE_DONE) {
    *_exists = false;
    return NS_OK;
  }

  return convertResultCode(srv);
}

bool Connection::findFunctionByInstance(mozIStorageFunction* aInstance) {
  sharedDBMutex.assertCurrentThreadOwns();

  for (const auto& data : mFunctions.Values()) {
    if (data.function == aInstance) {
      return true;
    }
  }
  return false;
}

/* static */
int Connection::sProgressHelper(void* aArg) {
  Connection* _this = static_cast<Connection*>(aArg);
  return _this->progressHandler();
}

int Connection::progressHandler() {
  sharedDBMutex.assertCurrentThreadOwns();
  if (mProgressHandler) {
    bool result;
    nsresult rv = mProgressHandler->OnProgress(this, &result);
    if (NS_FAILED(rv)) return 0;  // Don't break request
    return result ? 1 : 0;
  }
  return 0;
}

nsresult Connection::setClosedState() {
  // Flag that we are shutting down the async thread, so that
  // getAsyncExecutionTarget knows not to expose/create the async thread.
  MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
  NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);

  mAsyncExecutionThreadShuttingDown = true;

  // Set the property to null before closing the connection, otherwise the
  // other functions in the module may try to use the connection after it is
  // closed.
  mDBConn = nullptr;

  return NS_OK;
}

bool Connection::operationSupported(ConnectionOperation aOperationType) {
  if (aOperationType == ASYNCHRONOUS) {
    // Async operations are supported for all connections, on any thread.
    return true;
  }
  // Sync operations are supported for sync connections (on any thread), and
  // async connections on a background thread.
  MOZ_ASSERT(aOperationType == SYNCHRONOUS);
  return mSupportedOperations == SYNCHRONOUS || !NS_IsMainThread();
}

nsresult Connection::ensureOperationSupported(
    ConnectionOperation aOperationType) {
  if (NS_WARN_IF(!operationSupported(aOperationType))) {
#ifdef DEBUG
    if (NS_IsMainThread()) {
      nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
      Unused << xpc->DebugDumpJSStack(falsefalsefalse);
    }
#endif
    MOZ_ASSERT(false,
               "Don't use async connections synchronously on the main thread");
    return NS_ERROR_NOT_AVAILABLE;
  }
  return NS_OK;
}

bool Connection::isConnectionReadyOnThisThread() {
  MOZ_ASSERT_IF(connectionReady(), !mConnectionClosed);
  if (mAsyncExecutionThread && mAsyncExecutionThread->IsOnCurrentThread()) {
    return true;
  }
  return connectionReady();
}

bool Connection::isClosing() {
  MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
  return mAsyncExecutionThreadShuttingDown && !mConnectionClosed;
}

bool Connection::isClosed() {
  MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
  return mConnectionClosed;
}

bool Connection::isClosed(MutexAutoLock& lock) { return mConnectionClosed; }

bool Connection::isAsyncExecutionThreadAvailable() {
  MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn));
  return mAsyncExecutionThread && !mAsyncExecutionThreadShuttingDown;
}

void Connection::shutdownAsyncThread() {
  MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn));
  MOZ_ASSERT(mAsyncExecutionThread);
  MOZ_ASSERT(mAsyncExecutionThreadShuttingDown);

  MOZ_ALWAYS_SUCCEEDS(mAsyncExecutionThread->Shutdown());
  mAsyncExecutionThread = nullptr;
}

nsresult Connection::internalClose(sqlite3* aNativeConnection) {
#ifdef DEBUG
  {  // Make sure we have marked our async thread as shutting down.
    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
    MOZ_ASSERT(mAsyncExecutionThreadShuttingDown,
               "Did not call setClosedState!");
    MOZ_ASSERT(!isClosed(lockedScope), "Unexpected closed state");
  }
#endif  // DEBUG

  if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
    nsAutoCString leafName(":memory");
    if (mDatabaseFile) (void)mDatabaseFile->GetNativeLeafName(leafName);
    MOZ_LOG(gStorageLog, LogLevel::Debug,
            ("Closing connection to '%s'", leafName.get()));
  }

  // At this stage, we may still have statements that need to be
  // finalized. Attempt to close the database connection. This will
  // always disconnect any virtual tables and cleanly finalize their
  // internal statements. Once this is done, closing may fail due to
  // unfinalized client statements, in which case we need to finalize
  // these statements and close again.
  {
    MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
    mConnectionClosed = true;
  }

  // Nothing else needs to be done if we don't have a connection here.
  if (!aNativeConnection) return NS_OK;

  int srv = ::sqlite3_close(aNativeConnection);

  if (srv == SQLITE_BUSY) {
    {
      // Nothing else should change the connection or statements status until we
      // are done here.
      SQLiteMutexAutoLock lockedScope(sharedDBMutex);
      // We still have non-finalized statements. Finalize them.
      sqlite3_stmt* stmt = nullptr;
      while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) {
        MOZ_LOG(gStorageLog, LogLevel::Debug,
                ("Auto-finalizing SQL statement '%s' (%p)", ::sqlite3_sql(stmt),
                 stmt));

#ifdef DEBUG
        SmprintfPointer msg = ::mozilla::Smprintf(
            "SQL statement '%s' (%p) should have been finalized before closing "
            "the connection",
            ::sqlite3_sql(stmt), stmt);
        NS_WARNING(msg.get());
#endif  // DEBUG

        srv = ::sqlite3_finalize(stmt);

#ifdef DEBUG
        if (srv != SQLITE_OK) {
          SmprintfPointer msg = ::mozilla::Smprintf(
              "Could not finalize SQL statement (%p)", stmt);
          NS_WARNING(msg.get());
        }
#endif  // DEBUG

        // Ensure that the loop continues properly, whether closing has
        // succeeded or not.
        if (srv == SQLITE_OK) {
          stmt = nullptr;
        }
      }
      // Scope exiting will unlock the mutex before we invoke sqlite3_close()
      // again, since Sqlite will try to acquire it.
    }

    // Now that all statements have been finalized, we
    // should be able to close.
    srv = ::sqlite3_close(aNativeConnection);
    MOZ_ASSERT(false,
               "Had to forcibly close the database connection because not all "
               "the statements have been finalized.");
  }

  if (srv == SQLITE_OK) {
    sharedDBMutex.destroy();
  } else {
    MOZ_ASSERT(false,
               "sqlite3_close failed. There are probably outstanding "
               "statements that are listed above!");
  }

  return convertResultCode(srv);
}

nsCString Connection::getFilename() { return mTelemetryFilename; }

int Connection::stepStatement(sqlite3* aNativeConnection,
                              sqlite3_stmt* aStatement) {
  MOZ_ASSERT(aStatement);

  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::stepStatement", OTHER,
                                   ::sqlite3_sql(aStatement));

  bool checkedMainThread = false;
  TimeStamp startTime = TimeStamp::Now();

  // The connection may have been closed if the executing statement has been
  // created and cached after a call to asyncClose() but before the actual
  // sqlite3_close().  This usually happens when other tasks using cached
  // statements are asynchronously scheduled for execution and any of them ends
  // up after asyncClose. See bug 728653 for details.
  if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;

  (void)::sqlite3_extended_result_codes(aNativeConnection, 1);

  int srv;
  while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
    if (!checkedMainThread) {
      checkedMainThread = true;
      if (::NS_IsMainThread()) {
        NS_WARNING("We won't allow blocking on the main thread!");
        break;
      }
    }

    srv = WaitForUnlockNotify(aNativeConnection);
    if (srv != SQLITE_OK) {
      break;
    }

    ::sqlite3_reset(aStatement);
  }

  // Report very slow SQL statements to Telemetry
  TimeDuration duration = TimeStamp::Now() - startTime;
  const uint32_t threshold = NS_IsMainThread()
                                 ? Telemetry::kSlowSQLThresholdForMainThread
                                 : Telemetry::kSlowSQLThresholdForHelperThreads;
  if (duration.ToMilliseconds() >= threshold) {
    nsDependentCString statementString(::sqlite3_sql(aStatement));
    Telemetry::RecordSlowSQLStatement(
        statementString, mTelemetryFilename,
        static_cast<uint32_t>(duration.ToMilliseconds()));
  }

  (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
  // Drop off the extended result bits of the result code.
  return srv & 0xFF;
}

int Connection::prepareStatement(sqlite3* aNativeConnection,
                                 const nsCString& aSQL, sqlite3_stmt** _stmt) {
  // We should not even try to prepare statements after the connection has
  // been closed.
  if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;

  bool checkedMainThread = false;

  (void)::sqlite3_extended_result_codes(aNativeConnection, 1);

  int srv;
  while ((srv = ::sqlite3_prepare_v2(aNativeConnection, aSQL.get(), -1, _stmt,
                                     nullptr)) == SQLITE_LOCKED_SHAREDCACHE) {
    if (!checkedMainThread) {
      checkedMainThread = true;
      if (::NS_IsMainThread()) {
        NS_WARNING("We won't allow blocking on the main thread!");
        break;
      }
    }

    srv = WaitForUnlockNotify(aNativeConnection);
    if (srv != SQLITE_OK) {
      break;
    }
  }

  if (srv != SQLITE_OK) {
    nsCString warnMsg;
    warnMsg.AppendLiteral("The SQL statement '");
    warnMsg.Append(aSQL);
    warnMsg.AppendLiteral("' could not be compiled due to an error: ");
    warnMsg.Append(::sqlite3_errmsg(aNativeConnection));

#ifdef DEBUG
    NS_WARNING(warnMsg.get());
#endif
    MOZ_LOG(gStorageLog, LogLevel::Error, ("%s", warnMsg.get()));
  }

  (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
  // Drop off the extended result bits of the result code.
  int rc = srv & 0xFF;
  // sqlite will return OK on a comment only string and set _stmt to nullptr.
  // The callers of this function are used to only checking the return value,
  // so it is safer to return an error code.
  if (rc == SQLITE_OK && *_stmt == nullptr) {
    return SQLITE_MISUSE;
  }

  return rc;
}

int Connection::executeSql(sqlite3* aNativeConnection, const char* aSqlString) {
  if (!isConnectionReadyOnThisThread()) return SQLITE_MISUSE;

  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("Connection::executeSql", OTHER, aSqlString);

  TimeStamp startTime = TimeStamp::Now();
  int srv =
      ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr, nullptr);
  RecordQueryStatus(srv);

  // Report very slow SQL statements to Telemetry
  TimeDuration duration = TimeStamp::Now() - startTime;
  const uint32_t threshold = NS_IsMainThread()
                                 ? Telemetry::kSlowSQLThresholdForMainThread
                                 : Telemetry::kSlowSQLThresholdForHelperThreads;
  if (duration.ToMilliseconds() >= threshold) {
    nsDependentCString statementString(aSqlString);
    Telemetry::RecordSlowSQLStatement(
        statementString, mTelemetryFilename,
        static_cast<uint32_t>(duration.ToMilliseconds()));
  }

  return srv;
}

////////////////////////////////////////////////////////////////////////////////
//// nsIInterfaceRequestor

NS_IMETHODIMP
Connection::GetInterface(const nsIID& aIID, void** _result) {
  if (aIID.Equals(NS_GET_IID(nsIEventTarget))) {
    nsIEventTarget* background = getAsyncExecutionTarget();
    NS_IF_ADDREF(background);
    *_result = background;
    return NS_OK;
  }
  return NS_ERROR_NO_INTERFACE;
}

////////////////////////////////////////////////////////////////////////////////
//// mozIStorageConnection

NS_IMETHODIMP
Connection::Close() {
  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return synchronousClose();
}

nsresult Connection::synchronousClose() {
  if (!connectionReady()) {
    return NS_ERROR_NOT_INITIALIZED;
  }

#ifdef DEBUG
  // Since we're accessing mAsyncExecutionThread, we need to be on the opener
  // event target. We make this check outside of debug code below in
  // setClosedState, but this is here to be explicit.
  MOZ_ASSERT(IsOnCurrentSerialEventTarget(eventTargetOpenedOn));
#endif  // DEBUG

  // Make sure we have not executed any asynchronous statements.
  // If this fails, the mDBConn may be left open, resulting in a leak.
  // We'll try to finalize the pending statements and close the connection.
  if (isAsyncExecutionThreadAvailable()) {
#ifdef DEBUG
    if (NS_IsMainThread()) {
      nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
      Unused << xpc->DebugDumpJSStack(falsefalsefalse);
    }
#endif
    MOZ_ASSERT(false,
               "Close() was invoked on a connection that executed asynchronous "
               "statements. "
               "Should have used asyncClose().");
    // Try to close the database regardless, to free up resources.
    Unused << SpinningSynchronousClose();
    return NS_ERROR_UNEXPECTED;
  }

  // setClosedState nullifies our connection pointer, so we take a raw pointer
  // off it, to pass it through the close procedure.
  sqlite3* nativeConn = mDBConn;
  nsresult rv = setClosedState();
  NS_ENSURE_SUCCESS(rv, rv);

  return internalClose(nativeConn);
}

NS_IMETHODIMP
Connection::SpinningSynchronousClose() {
  nsresult rv = ensureOperationSupported(SYNCHRONOUS);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!IsOnCurrentSerialEventTarget(eventTargetOpenedOn)) {
    return NS_ERROR_NOT_SAME_THREAD;
  }

  // As currently implemented, we can't spin to wait for an existing AsyncClose.
  // Our only existing caller will never have called close; assert if misused
  // so that no new callers assume this works after an AsyncClose.
  MOZ_DIAGNOSTIC_ASSERT(connectionReady());
  if (!connectionReady()) {
    return NS_ERROR_UNEXPECTED;
  }

  RefPtr<CloseListener> listener = new CloseListener();
  rv = AsyncClose(listener);
  NS_ENSURE_SUCCESS(rv, rv);
  MOZ_ALWAYS_TRUE(
      SpinEventLoopUntil("storage::Connection::SpinningSynchronousClose"_ns,
                         [&]() { return listener->mClosed; }));
  MOZ_ASSERT(isClosed(), "The connection should be closed at this point");

  return rv;
}

NS_IMETHODIMP
Connection::AsyncClose(mozIStorageCompletionCallback* aCallback) {
  NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
  // Check if AsyncClose or Close were already invoked.
  if (!connectionReady()) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  nsresult rv = ensureOperationSupported(ASYNCHRONOUS);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // The two relevant factors at this point are whether we have a database
  // connection and whether we have an async execution thread.  Here's what the
  // states mean and how we handle them:
  //
  // - (mDBConn && asyncThread): The expected case where we are either an
  //   async connection or a sync connection that has been used asynchronously.
  //   Either way the caller must call us and not Close().  Nothing surprising
  //   about this.  We'll dispatch AsyncCloseConnection to the already-existing
  //   async thread.
  //
  // - (mDBConn && !asyncThread): A somewhat unusual case where the caller
  //   opened the connection synchronously and was planning to use it
  //   asynchronously, but never got around to using it asynchronously before
  //   needing to shutdown.  This has been observed to happen for the cookie
  //   service in a case where Firefox shuts itself down almost immediately
  //   after startup (for unknown reasons).  In the Firefox shutdown case,
  //   we may also fail to create a new async execution thread if one does not
  //   already exist.  (nsThreadManager will refuse to create new threads when
  //   it has already been told to shutdown.)  As such, we need to handle a
  //   failure to create the async execution thread by falling back to
  //   synchronous Close() and also dispatching the completion callback because
  //   at least Places likes to spin a nested event loop that depends on the
  //   callback being invoked.
  //
  //   Note that we have considered not trying to spin up the async execution
  //   thread in this case if it does not already exist, but the overhead of
  //   thread startup (if successful) is significantly less expensive than the
  //   worst-case potential I/O hit of synchronously closing a database when we
  //   could close it asynchronously.
  //
  // - (!mDBConn && asyncThread): This happens in some but not all cases where
  //   OpenAsyncDatabase encountered a problem opening the database.  If it
  //   happened in all cases AsyncInitDatabase would just shut down the thread
  //   directly and we would avoid this case.  But it doesn't, so for simplicity
  //   and consistency AsyncCloseConnection knows how to handle this and we
  //   act like this was the (mDBConn && asyncThread) case in this method.
  //
  // - (!mDBConn && !asyncThread): The database was never successfully opened or
  //   Close() or AsyncClose() has already been called (at least) once.  This is
  //   undeniably a misuse case by the caller.  We could optimize for this
  //   case by adding an additional check of mAsyncExecutionThread without using
  //   getAsyncExecutionTarget() to avoid wastefully creating a thread just to
  //   shut it down.  But this complicates the method for broken caller code
  //   whereas we're still correct and safe without the special-case.
  nsIEventTarget* asyncThread = getAsyncExecutionTarget();

  // Create our callback event if we were given a callback.  This will
  // eventually be dispatched in all cases, even if we fall back to Close() and
  // the database wasn't open and we return an error.  The rationale is that
  // no existing consumer checks our return value and several of them like to
  // spin nested event loops until the callback fires.  Given that, it seems
  // preferable for us to dispatch the callback in all cases.  (Except the
  // wrong thread misuse case we bailed on up above.  But that's okay because
  // that is statically wrong whereas these edge cases are dynamic.)
  nsCOMPtr<nsIRunnable> completeEvent;
  if (aCallback) {
    completeEvent = newCompletionEvent(aCallback);
  }

  if (!asyncThread) {
    // We were unable to create an async thread, so we need to fall back to
--> --------------------

--> maximum size reached

--> --------------------

96%


¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.81Bemerkung:  (vorverarbeitet)  ¤

*Bot Zugriff






Normalansicht

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.