/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "CrashReporterHost.h"
#include "CrashReporterMetadataShmem.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Sprintf.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#ifdef MOZ_CRASHREPORTER
# include "nsIAsyncShutdown.h"
# include "nsICrashService.h"
#endif

namespace mozilla {
namespace ipc {

CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType,
                                     const Shmem& aShmem)
 : mProcessType(aProcessType),
   mShmem(aShmem),
   mStartTime(::time(nullptr))
{
}

#ifdef MOZ_CRASHREPORTER
void
CrashReporterHost::GenerateCrashReport(RefPtr<nsIFile> aCrashDump)
{
  nsString dumpID;
  if (!CrashReporter::GetIDFromMinidump(aCrashDump, dumpID)) {
    return;
  }

  CrashReporter::AnnotationTable notes;

  nsAutoCString type;
  switch (mProcessType) {
    case GeckoProcessType_Content:
      type = NS_LITERAL_CSTRING("content");
      break;
    case GeckoProcessType_Plugin:
    case GeckoProcessType_GMPlugin:
      type = NS_LITERAL_CSTRING("plugin");
      break;
    case GeckoProcessType_GPU:
      type = NS_LITERAL_CSTRING("gpu");
      break;
    default:
      NS_ERROR("unknown process type");
      break;
  }
  notes.Put(NS_LITERAL_CSTRING("ProcessType"), type);

  char startTime[32];
  SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime));
  notes.Put(NS_LITERAL_CSTRING("StartupTime"), nsDependentCString(startTime));

  CrashReporterMetadataShmem::ReadAppNotes(mShmem, &notes);

  CrashReporter::AppendExtraData(dumpID, notes);
  NotifyCrashService(mProcessType, dumpID, &notes);
}

/**
 * Runnable used to execute the minidump analyzer program asynchronously after
 * a crash. This should run on a background thread not to block the main thread
 * over the potentially long minidump analysis. Once analysis is over, the
 * crash information will be relayed to the crash manager via another runnable
 * sent to the main thread. Shutdown will be blocked for the duration of the
 * entire process to ensure this information is sent.
 */
class AsyncMinidumpAnalyzer final : public nsIRunnable
                                  , public nsIAsyncShutdownBlocker
{
public:
  /**
   * Create a new minidump analyzer runnable, this will also block shutdown
   * until the associated crash has been added to the crash manager.
   */
  AsyncMinidumpAnalyzer(int32_t aProcessType,
                        int32_t aCrashType,
                        const nsString& aChildDumpID)
    : mProcessType(aProcessType)
    , mCrashType(aCrashType)
    , mChildDumpID(aChildDumpID)
    , mName(NS_LITERAL_STRING("Crash reporter: blocking on minidump analysis"))
  {
    MOZ_ASSERT(NS_IsMainThread());

    nsresult rv = GetShutdownBarrier()->AddBlocker(
      this, NS_LITERAL_STRING(__FILE__), __LINE__,
      NS_LITERAL_STRING("Minidump analysis"));
    Unused << NS_WARN_IF(NS_FAILED(rv));
  }

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

    if (mProcessType == nsICrashService::PROCESS_TYPE_CONTENT) {
      CrashReporter::RunMinidumpAnalyzer(mChildDumpID);
    }

    // Make a copy of these so we can copy them into the runnable lambda
    int32_t processType = mProcessType;
    int32_t crashType = mCrashType;
    nsString childDumpID(mChildDumpID);
    nsCOMPtr<nsIAsyncShutdownBlocker> self(this);

    NS_DispatchToMainThread(NS_NewRunnableFunction([
      self, processType, crashType, childDumpID
    ] {
      nsCOMPtr<nsICrashService> crashService =
        do_GetService("@mozilla.org/crashservice;1");
      if (crashService) {
        crashService->AddCrash(processType, crashType, childDumpID);
      }

      nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();

      if (barrier) {
        barrier->RemoveBlocker(self);
      }
    }));

    return NS_OK;
  }

  NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aBarrierClient) override
  {
    return NS_OK;
  }

  NS_IMETHOD GetName(nsAString& aName) override
  {
    aName = mName;
    return NS_OK;
  }

  NS_IMETHOD GetState(nsIPropertyBag**) override
  {
    return NS_OK;
  }

  NS_DECL_THREADSAFE_ISUPPORTS

private:
  ~AsyncMinidumpAnalyzer() {}

  // Returns the profile-before-change shutdown barrier
  static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier()
  {
    MOZ_ASSERT(NS_IsMainThread());

    nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown();
    nsCOMPtr<nsIAsyncShutdownClient> barrier;
    nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));

    if (NS_WARN_IF(NS_FAILED(rv))) {
      return nullptr;
    }

    return barrier.forget();
  }

  int32_t mProcessType;
  int32_t mCrashType;
  const nsString mChildDumpID;
  const nsString mName;
};

NS_IMPL_ISUPPORTS(AsyncMinidumpAnalyzer, nsIRunnable, nsIAsyncShutdownBlocker)

/**
 * Add information about a crash to the crash manager. This method runs the
 * various activities required to gather additional information and notify the
 * crash manager asynchronously, since many of them involve I/O or potentially
 * long processing.
 *
 * @param aProcessType The type of process that crashed
 * @param aCrashType The type of crash (crash or hang)
 * @param aChildDumpID A string holding the ID of the dump associated with this
 *        crash
 */
/* static */ void
CrashReporterHost::AsyncAddCrash(int32_t aProcessType,
                                 int32_t aCrashType,
                                 const nsString& aChildDumpID)
{
  MOZ_ASSERT(NS_IsMainThread());

  static StaticRefPtr<LazyIdleThread> sBackgroundThread;

  if (!sBackgroundThread) {
    // Background thread used for running minidump analysis. It will be
    // destroyed immediately after it's done with the task.
    sBackgroundThread =
      new LazyIdleThread(0, NS_LITERAL_CSTRING("CrashReporterHost"));
    ClearOnShutdown(&sBackgroundThread);
  }

  RefPtr<AsyncMinidumpAnalyzer> task =
    new AsyncMinidumpAnalyzer(aProcessType, aCrashType, aChildDumpID);

  Unused << sBackgroundThread->Dispatch(task, NS_DISPATCH_NORMAL);
}

/* static */ void
CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
                                      const nsString& aChildDumpID,
                                      const AnnotationTable* aNotes)
{
  if (!NS_IsMainThread()) {
    RefPtr<Runnable> runnable = NS_NewRunnableFunction([=] () -> void {
      CrashReporterHost::NotifyCrashService(aProcessType, aChildDumpID, aNotes);
    });
    RefPtr<nsIThread> mainThread = do_GetMainThread();
    SyncRunnable::DispatchToThread(mainThread, runnable);
    return;
  }

  MOZ_ASSERT(!aChildDumpID.IsEmpty());

  int32_t processType;
  int32_t crashType = nsICrashService::CRASH_TYPE_CRASH;

  nsCString telemetryKey;

  switch (aProcessType) {
    case GeckoProcessType_Content:
      processType = nsICrashService::PROCESS_TYPE_CONTENT;
      telemetryKey.AssignLiteral("content");
      break;
    case GeckoProcessType_Plugin: {
      processType = nsICrashService::PROCESS_TYPE_PLUGIN;
      telemetryKey.AssignLiteral("plugin");
      nsAutoCString val;
      if (aNotes->Get(NS_LITERAL_CSTRING("PluginHang"), &val) &&
        val.Equals(NS_LITERAL_CSTRING("1"))) {
        crashType = nsICrashService::CRASH_TYPE_HANG;
        telemetryKey.AssignLiteral("pluginhang");
      }
      break;
    }
    case GeckoProcessType_GMPlugin:
      processType = nsICrashService::PROCESS_TYPE_GMPLUGIN;
      telemetryKey.AssignLiteral("gmplugin");
      break;
    case GeckoProcessType_GPU:
      processType = nsICrashService::PROCESS_TYPE_GPU;
      telemetryKey.AssignLiteral("gpu");
      break;
    default:
      NS_ERROR("unknown process type");
      return;
  }

  AsyncAddCrash(processType, crashType, aChildDumpID);
  Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
}
#endif

} // namespace ipc
} // namespace mozilla
