/* -*- 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 https://mozilla.org/MPL/2.0/. */

#include "ErrorHandler.h"

#include <utility>

#include "mozilla/ArrayUtils.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/WinTokenUtils.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/XREAppData.h"
#include "mozilla/glue/WindowsDllServices.h"
#include "mozilla/mscom/ProcessRuntime.h"
#include "nsWindowsHelpers.h"

#if defined(MOZ_LAUNCHER_PROCESS)
#  include "mozilla/LauncherRegistryInfo.h"
#endif  // defined(MOZ_LAUNCHER_PROCESS)

#include <algorithm>
#include <process.h>
#include <sstream>
#include <string>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <objbase.h>
#include <rpc.h>
#if !defined(__MINGW32__)
#  include <comutil.h>
#  include <iwscapi.h>
#  include <wscapi.h>
#endif  // !defined(__MINGW32__)
#include <tlhelp32.h>

#if !defined(RRF_SUBKEY_WOW6464KEY)
#  define RRF_SUBKEY_WOW6464KEY 0x00010000
#endif  // !defined(RRF_SUBKEY_WOW6464KEY)

#define QUOTE_ME2(x) #x
#define QUOTE_ME(x) QUOTE_ME2(x)

#define TELEMETRY_BASE_URL L"https://incoming.telemetry.mozilla.org/submit"
#define TELEMETRY_NAMESPACE L"/firefox-launcher-process"
#define TELEMETRY_LAUNCHER_PING_DOCTYPE L"/launcher-process-failure"
#define TELEMETRY_LAUNCHER_PING_VERSION L"/1"

static const wchar_t kUrl[] = TELEMETRY_BASE_URL TELEMETRY_NAMESPACE
    TELEMETRY_LAUNCHER_PING_DOCTYPE TELEMETRY_LAUNCHER_PING_VERSION L"/";
static const uint32_t kGuidCharLenWithNul = 39;
static const uint32_t kGuidCharLenNoBracesNoNul = 36;
static const mozilla::StaticXREAppData* gAppData;

// Ordinarily, errors are only reported to the Windows Event Log when they are
// not reported upstream via telemetry (usually due either to telemetry being
// disabled or to network failure).
//
// If `--log-launcher-error` is given at the command line, launcher errors will
// always be reported to the Windows Event Log, regardless of whether or not
// they're sent upstream.
static bool gForceEventLog = false;

namespace {

constexpr wchar_t kEventSourceName[] = L"" MOZ_APP_DISPLAYNAME " Launcher";

struct EventSourceDeleter {
  using pointer = HANDLE;

  void operator()(pointer aEvtSrc) { ::DeregisterEventSource(aEvtSrc); }
};

using EventLog = mozilla::UniquePtr<HANDLE, EventSourceDeleter>;

struct SerializedEventData {
  HRESULT mHr;
  uint32_t mLine;
  char mFile[1];
};

}  // anonymous namespace

static void PostErrorToLog(const mozilla::LauncherError& aError) {
  // This is very bare-bones; just enough to spit out an HRESULT to the
  // Application event log.
  EventLog log(::RegisterEventSourceW(nullptr, kEventSourceName));

  if (!log) {
    return;
  }

  size_t fileLen = strlen(aError.mFile);
  size_t dataLen = sizeof(HRESULT) + sizeof(uint32_t) + fileLen;
  auto evtDataBuf = mozilla::MakeUnique<char[]>(dataLen);
  SerializedEventData& evtData =
      *reinterpret_cast<SerializedEventData*>(evtDataBuf.get());
  evtData.mHr = aError.mError.AsHResult();
  evtData.mLine = aError.mLine;
  // Since this is binary data, we're not concerning ourselves with null
  // terminators.
  memcpy(evtData.mFile, aError.mFile, fileLen);

  ::ReportEventW(log.get(), EVENTLOG_ERROR_TYPE, 0, aError.mError.AsHResult(),
                 nullptr, 0, dataLen, nullptr, evtDataBuf.get());
}

#if defined(MOZ_TELEMETRY_REPORTING)

namespace {

// This JSONWriteFunc writes directly to a temp file. By creating this file
// with the FILE_ATTRIBUTE_TEMPORARY attribute, we hint to the OS that this
// file is short-lived. The OS will try to avoid flushing it to disk if at
// all possible.
class TempFileWriter final : public mozilla::JSONWriteFunc {
 public:
  TempFileWriter() : mFailed(false), mSuccessfulHandoff(false) {
    wchar_t name[MAX_PATH + 1] = {};
    if (_wtmpnam_s(name)) {
      mFailed = true;
      return;
    }

    mTempFileName = name;

    mTempFile.own(::CreateFileW(name, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
                                CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr));
    if (mTempFile.get() == INVALID_HANDLE_VALUE) {
      mFailed = true;
    }
  }

  ~TempFileWriter() {
    if (mSuccessfulHandoff) {
      // It is no longer our responsibility to delete the temp file if we have
      // successfully handed it off to pingsender.
      return;
    }

    mTempFile.reset();
    ::DeleteFileW(mTempFileName.c_str());
  }

  explicit operator bool() const { return !mFailed; }

  void Write(const mozilla::Span<const char>& aStr) final {
    if (mFailed) {
      return;
    }

    DWORD bytesWritten = 0;
    if (!::WriteFile(mTempFile, aStr.data(), aStr.size(), &bytesWritten,
                     nullptr) ||
        bytesWritten != aStr.size()) {
      mFailed = true;
    }
  }

  const std::wstring& GetFileName() const { return mTempFileName; }

  void SetSuccessfulHandoff() { mSuccessfulHandoff = true; }

 private:
  bool mFailed;
  bool mSuccessfulHandoff;
  std::wstring mTempFileName;
  nsAutoHandle mTempFile;
};

using SigMap = mozilla::Vector<std::wstring, 0, InfallibleAllocPolicy>;

}  // anonymous namespace

// This is the guideline for maximum string length for telemetry intake
static const size_t kMaxStrLen = 80;

static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
                                             const size_t aStrLenExclNul) {
  // Yes, this might not handle surrogate pairs correctly. Let's just let
  // WideCharToMultiByte fail in that unlikely case.
  size_t cvtLen = std::min(aStrLenExclNul, kMaxStrLen);

  int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, nullptr, 0,
                                      nullptr, nullptr);
  if (!numConv) {
    return nullptr;
  }

  // Include room for the null terminator by adding one
  auto buf = mozilla::MakeUnique<char[]>(numConv + 1);

  numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, buf.get(), numConv,
                                  nullptr, nullptr);
  if (!numConv) {
    return nullptr;
  }

  // Add null termination. numConv does not include the terminator, so we don't
  // subtract 1 when indexing into buf.
  buf[numConv] = 0;

  return buf;
}

static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
  return WideToUTF8(aStr, wcslen(aStr));
}

static mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
  return WideToUTF8(aStr.c_str(), aStr.length());
}

// MinGW does not support the Windows Security Center APIs.
#  if !defined(__MINGW32__)

static mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
  return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
}

namespace {

struct ProviderKey {
  WSC_SECURITY_PROVIDER mProviderType;
  const char* mKey;
};

}  // anonymous namespace

static bool EnumWSCProductList(RefPtr<IWSCProductList>& aProdList,
                               mozilla::JSONWriter& aJson) {
  LONG count;
  HRESULT hr = aProdList->get_Count(&count);
  if (FAILED(hr)) {
    return false;
  }

  // Unlikely, but put a bound on the max length of the output array for the
  // purposes of telemetry intake.
  count = std::min(count, 1000L);

  // Record the name(s) of each active registered product in this category
  for (LONG index = 0; index < count; ++index) {
    RefPtr<IWscProduct> product;
    hr = aProdList->get_Item(index, getter_AddRefs(product));
    if (FAILED(hr)) {
      return false;
    }

    WSC_SECURITY_PRODUCT_STATE state;
    hr = product->get_ProductState(&state);
    if (FAILED(hr)) {
      return false;
    }

    // We only care about products that are active
    if (state == WSC_SECURITY_PRODUCT_STATE_OFF ||
        state == WSC_SECURITY_PRODUCT_STATE_SNOOZED ||
        state == WSC_SECURITY_PRODUCT_STATE_EXPIRED) {
      continue;
    }

    _bstr_t bName;
    hr = product->get_ProductName(bName.GetAddress());
    if (FAILED(hr)) {
      return false;
    }

    auto buf = WideToUTF8(bName);
    if (!buf) {
      return false;
    }

    aJson.StringElement(mozilla::MakeStringSpan(buf.get()));
  }

  return true;
}

static const ProviderKey gProvKeys[] = {
    {WSC_SECURITY_PROVIDER_ANTIVIRUS, "av"},
    {WSC_SECURITY_PROVIDER_ANTISPYWARE, "antispyware"},
    {WSC_SECURITY_PROVIDER_FIREWALL, "firewall"}};

static bool AddWscInfo(mozilla::JSONWriter& aJson) {
  if (!mozilla::IsWin8OrLater()) {
    // We haven't written anything yet, so we can return true here and continue
    // capturing data.
    return true;
  }

  // We need COM for this. Using ProcessRuntime so that process-global COM
  // configuration is done correctly
  mozilla::mscom::ProcessRuntime mscom(
      mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
  if (!mscom) {
    // We haven't written anything yet, so we can return true here and continue
    // capturing data.
    return true;
  }

  aJson.StartObjectProperty("security");

  const CLSID clsid = __uuidof(WSCProductList);
  const IID iid = __uuidof(IWSCProductList);

  for (uint32_t index = 0; index < mozilla::ArrayLength(gProvKeys); ++index) {
    // NB: A separate instance of IWSCProductList is needed for each distinct
    // security provider type; MSDN says that we cannot reuse the same object
    // and call Initialize() to pave over the previous data.
    RefPtr<IWSCProductList> prodList;
    HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid,
                                    getter_AddRefs(prodList));
    if (FAILED(hr)) {
      return false;
    }

    hr = prodList->Initialize(gProvKeys[index].mProviderType);
    if (FAILED(hr)) {
      return false;
    }

    aJson.StartArrayProperty(mozilla::MakeStringSpan(gProvKeys[index].mKey));

    if (!EnumWSCProductList(prodList, aJson)) {
      return false;
    }

    aJson.EndArray();
  }

  aJson.EndObject();

  return true;
}
#  endif  // !defined(__MINGW32__)

// Max array length for telemetry intake.
static const size_t kMaxArrayLen = 1000;

static bool AddModuleInfo(const nsAutoHandle& aSnapshot,
                          mozilla::JSONWriter& aJson) {
  if (aSnapshot.get() == INVALID_HANDLE_VALUE) {
    // We haven't written anything yet, so we can return true here and continue
    // capturing data.
    return true;
  }

  SigMap signatures;
  size_t moduleCount = 0;

  MODULEENTRY32W module = {sizeof(module)};
  if (!::Module32FirstW(aSnapshot, &module)) {
    // We haven't written anything yet, so we can return true here and continue
    // capturing data.
    return true;
  }

  mozilla::glue::BasicDllServices dllServices;

  aJson.StartObjectProperty("modules");

  // For each module, add its version number (or empty string if not present),
  // followed by an optional index into the signatures array
  do {
    ++moduleCount;

    wchar_t leaf[_MAX_FNAME] = {};
    if (::_wsplitpath_s(module.szExePath, nullptr, 0, nullptr, 0, leaf,
                        mozilla::ArrayLength(leaf), nullptr, 0)) {
      return false;
    }

    if (_wcslwr_s(leaf, mozilla::ArrayLength(leaf))) {
      return false;
    }

    auto leafUtf8 = WideToUTF8(leaf);
    if (!leafUtf8) {
      return false;
    }

    aJson.StartArrayProperty(mozilla::MakeStringSpan(leafUtf8.get()));

    std::string version;
    DWORD verInfoSize = ::GetFileVersionInfoSizeW(module.szExePath, nullptr);
    if (verInfoSize) {
      auto verInfoBuf = mozilla::MakeUnique<BYTE[]>(verInfoSize);

      if (::GetFileVersionInfoW(module.szExePath, 0, verInfoSize,
                                verInfoBuf.get())) {
        VS_FIXEDFILEINFO* fixedInfo = nullptr;
        UINT fixedInfoLen = 0;

        if (::VerQueryValueW(verInfoBuf.get(), L"\\",
                             reinterpret_cast<LPVOID*>(&fixedInfo),
                             &fixedInfoLen)) {
          std::ostringstream oss;
          oss << HIWORD(fixedInfo->dwFileVersionMS) << '.'
              << LOWORD(fixedInfo->dwFileVersionMS) << '.'
              << HIWORD(fixedInfo->dwFileVersionLS) << '.'
              << LOWORD(fixedInfo->dwFileVersionLS);
          version = oss.str();
        }
      }
    }

    aJson.StringElement(version);

    mozilla::Maybe<ptrdiff_t> sigIndex;
    auto signedBy = dllServices.GetBinaryOrgName(module.szExePath);
    if (signedBy) {
      std::wstring strSignedBy(signedBy.get());
      auto entry = std::find(signatures.begin(), signatures.end(), strSignedBy);
      if (entry == signatures.end()) {
        mozilla::Unused << signatures.append(std::move(strSignedBy));
        entry = &signatures.back();
      }

      sigIndex = mozilla::Some(entry - signatures.begin());
    }

    if (sigIndex) {
      aJson.IntElement(sigIndex.value());
    }

    aJson.EndArray();
  } while (moduleCount < kMaxArrayLen && ::Module32NextW(aSnapshot, &module));

  aJson.EndObject();

  aJson.StartArrayProperty("signatures");

  // Serialize each entry in the signatures array
  for (auto&& itr : signatures) {
    auto sigUtf8 = WideToUTF8(itr);
    if (!sigUtf8) {
      continue;
    }

    aJson.StringElement(mozilla::MakeStringSpan(sigUtf8.get()));
  }

  aJson.EndArray();

  return true;
}

namespace {

struct PingThreadContext {
  explicit PingThreadContext(const mozilla::LauncherError& aError,
                             const char* aProcessType)
      : mLauncherError(aError),
        mModulesSnapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)),
        mProcessType(aProcessType ? aProcessType : "") {}
  mozilla::LauncherError mLauncherError;
  nsAutoHandle mModulesSnapshot;
  std::string mProcessType;
};

}  // anonymous namespace

static bool PrepPing(const PingThreadContext& aContext, const std::wstring& aId,
                     mozilla::JSONWriter& aJson) {
#  if defined(DEBUG)
  const mozilla::JSONWriter::CollectionStyle style =
      mozilla::JSONWriter::MultiLineStyle;
#  else
  const mozilla::JSONWriter::CollectionStyle style =
      mozilla::JSONWriter::SingleLineStyle;
#  endif  // defined(DEBUG)

  aJson.Start(style);

  aJson.StringProperty("type", "launcher-process-failure");
  aJson.IntProperty("version", 1);

  auto idUtf8 = WideToUTF8(aId);
  if (idUtf8) {
    aJson.StringProperty("id", mozilla::MakeStringSpan(idUtf8.get()));
  }

  time_t now;
  time(&now);
  tm gmTm;
  if (!gmtime_s(&gmTm, &now)) {
    char isoTimeBuf[32] = {};
    if (strftime(isoTimeBuf, mozilla::ArrayLength(isoTimeBuf), "%FT%T.000Z",
                 &gmTm)) {
      aJson.StringProperty("creationDate", isoTimeBuf);
    }
  }

  aJson.StringProperty("update_channel", QUOTE_ME(MOZ_UPDATE_CHANNEL));

  if (gAppData) {
    aJson.StringProperty("build_id",
                         mozilla::MakeStringSpan(gAppData->buildID));
    aJson.StringProperty("build_version",
                         mozilla::MakeStringSpan(gAppData->version));
  }

  OSVERSIONINFOEXW osv = {sizeof(osv)};
  if (::GetVersionExW(reinterpret_cast<OSVERSIONINFOW*>(&osv))) {
    std::ostringstream oss;
    oss << osv.dwMajorVersion << "." << osv.dwMinorVersion << "."
        << osv.dwBuildNumber;

    if (osv.dwMajorVersion == 10 && osv.dwMinorVersion == 0) {
      // Get the "Update Build Revision" (UBR) value
      DWORD ubrValue;
      DWORD ubrValueLen = sizeof(ubrValue);
      LSTATUS ubrOk =
          ::RegGetValueW(HKEY_LOCAL_MACHINE,
                         L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
                         L"UBR", RRF_RT_DWORD | RRF_SUBKEY_WOW6464KEY, nullptr,
                         &ubrValue, &ubrValueLen);
      if (ubrOk == ERROR_SUCCESS) {
        oss << "." << ubrValue;
      }
    }

    if (oss) {
      aJson.StringProperty("os_version", oss.str());
    }

    bool isServer = osv.wProductType == VER_NT_DOMAIN_CONTROLLER ||
                    osv.wProductType == VER_NT_SERVER;
    aJson.BoolProperty("server_os", isServer);
  }

  WCHAR localeName[LOCALE_NAME_MAX_LENGTH] = {};
  int localeNameLen =
      ::GetUserDefaultLocaleName(localeName, mozilla::ArrayLength(localeName));
  if (localeNameLen) {
    auto localeNameUtf8 = WideToUTF8(localeName, localeNameLen - 1);
    if (localeNameUtf8) {
      aJson.StringProperty("os_locale",
                           mozilla::MakeStringSpan(localeNameUtf8.get()));
    }
  }

  SYSTEM_INFO sysInfo;
  ::GetNativeSystemInfo(&sysInfo);
  aJson.IntProperty("cpu_arch", sysInfo.wProcessorArchitecture);
  aJson.IntProperty("num_logical_cpus", sysInfo.dwNumberOfProcessors);

  mozilla::LauncherResult<bool> isAdminWithoutUac =
      mozilla::IsAdminWithoutUac();
  if (isAdminWithoutUac.isOk()) {
    aJson.BoolProperty("is_admin_without_uac", isAdminWithoutUac.unwrap());
  }

  if (!aContext.mProcessType.empty()) {
    aJson.StringProperty("process_type", aContext.mProcessType);
  }

  MEMORYSTATUSEX memStatus = {sizeof(memStatus)};
  if (::GlobalMemoryStatusEx(&memStatus)) {
    aJson.StartObjectProperty("memory");
    aJson.IntProperty("total_phys", memStatus.ullTotalPhys);
    aJson.IntProperty("avail_phys", memStatus.ullAvailPhys);
    aJson.IntProperty("avail_page_file", memStatus.ullAvailPageFile);
    aJson.IntProperty("avail_virt", memStatus.ullAvailVirtual);
    aJson.EndObject();
  }

  aJson.StringProperty("xpcom_abi", TARGET_XPCOM_ABI);

  aJson.StartObjectProperty("launcher_error", style);

  std::string srcFileLeaf(aContext.mLauncherError.mFile);
  // Obtain the leaf name of the file for privacy reasons
  // (In case this is somebody's local build)
  auto pos = srcFileLeaf.find_last_of("/\\");
  if (pos != std::string::npos) {
    srcFileLeaf = srcFileLeaf.substr(pos + 1);
  }

  aJson.StringProperty("source_file", srcFileLeaf);

  aJson.IntProperty("source_line", aContext.mLauncherError.mLine);
  aJson.IntProperty("hresult", aContext.mLauncherError.mError.AsHResult());

#  if defined(NIGHTLY_BUILD)
  if (aContext.mLauncherError.mDetourError.isSome()) {
    static const char* kHexMap = "0123456789abcdef";
    char hexStr[sizeof(mozilla::DetourError::mOrigBytes) * 2 + 1];
    int cnt = 0;
    for (uint8_t byte : aContext.mLauncherError.mDetourError->mOrigBytes) {
      hexStr[cnt++] = kHexMap[(byte >> 4) & 0x0f];
      hexStr[cnt++] = kHexMap[byte & 0x0f];
    }
    hexStr[cnt] = 0;
    aJson.StringProperty("detour_orig_bytes", hexStr);
  }
#  endif  // defined(NIGHTLY_BUILD)

  aJson.EndObject();

#  if !defined(__MINGW32__)
  if (!AddWscInfo(aJson)) {
    return false;
  }
#  endif  // !defined(__MINGW32__)

  if (!AddModuleInfo(aContext.mModulesSnapshot, aJson)) {
    return false;
  }

  aJson.End();

  return true;
}

static bool DoSendPing(const PingThreadContext& aContext) {
  TempFileWriter tempFile;
  mozilla::JSONWriter json(tempFile);

  UUID uuid;
  if (::UuidCreate(&uuid) != RPC_S_OK) {
    return false;
  }

  wchar_t guidBuf[kGuidCharLenWithNul] = {};
  if (::StringFromGUID2(uuid, guidBuf, kGuidCharLenWithNul) !=
      kGuidCharLenWithNul) {
    return false;
  }

  // Strip the curly braces off of the guid
  std::wstring guidNoBraces(guidBuf + 1, kGuidCharLenNoBracesNoNul);

  // Populate json with the ping information
  if (!PrepPing(aContext, guidNoBraces, json)) {
    return false;
  }

  // Obtain the name of the temp file that we have written
  const std::wstring& fileName = tempFile.GetFileName();

  // Using the path to our executable binary, construct the path to
  // pingsender.exe
  mozilla::UniquePtr<wchar_t[]> exePath(mozilla::GetFullBinaryPath());

  wchar_t drive[_MAX_DRIVE] = {};
  wchar_t dir[_MAX_DIR] = {};
  if (_wsplitpath_s(exePath.get(), drive, mozilla::ArrayLength(drive), dir,
                    mozilla::ArrayLength(dir), nullptr, 0, nullptr, 0)) {
    return false;
  }

  wchar_t pingSenderPath[MAX_PATH + 1] = {};
  if (_wmakepath_s(pingSenderPath, mozilla::ArrayLength(pingSenderPath), drive,
                   dir, L"pingsender", L"exe")) {
    return false;
  }

  // Construct the telemetry URL
  wchar_t urlBuf[mozilla::ArrayLength(kUrl) + kGuidCharLenNoBracesNoNul] = {};
  if (wcscpy_s(urlBuf, kUrl)) {
    return false;
  }

  if (wcscat_s(urlBuf, guidNoBraces.c_str())) {
    return false;
  }

  // Now build the command line arguments to pingsender
  wchar_t* pingSenderArgv[] = {pingSenderPath, urlBuf,
                               const_cast<wchar_t*>(fileName.c_str())};

  mozilla::UniquePtr<wchar_t[]> pingSenderCmdLine(mozilla::MakeCommandLine(
      mozilla::ArrayLength(pingSenderArgv), pingSenderArgv));

  // Now start pingsender to handle the rest
  PROCESS_INFORMATION pi;

  STARTUPINFOW si = {sizeof(si)};
  si.dwFlags = STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;

  if (!::CreateProcessW(pingSenderPath, pingSenderCmdLine.get(), nullptr,
                        nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
    return false;
  }

  tempFile.SetSuccessfulHandoff();

  nsAutoHandle proc(pi.hProcess);
  nsAutoHandle thread(pi.hThread);

  return true;
}

static unsigned __stdcall SendPingThread(void* aContext) {
  mozilla::UniquePtr<PingThreadContext> context(
      reinterpret_cast<PingThreadContext*>(aContext));

  if (!DoSendPing(*context) || gForceEventLog) {
    PostErrorToLog(context->mLauncherError);
  }

  return 0;
}

#endif  // defined(MOZ_TELEMETRY_REPORTING)

static bool SendPing(const mozilla::LauncherError& aError,
                     const char* aProcessType) {
#if defined(MOZ_TELEMETRY_REPORTING)
#  if defined(MOZ_LAUNCHER_PROCESS)
  mozilla::LauncherRegistryInfo regInfo;
  mozilla::LauncherResult<bool> telemetryEnabled = regInfo.IsTelemetryEnabled();
  if (telemetryEnabled.isErr() || !telemetryEnabled.unwrap()) {
    // Do not send anything if telemetry has been opted out
    return false;
  }
#  endif  // defined(MOZ_LAUNCHER_PROCESS)

  // We send this ping when the launcher process fails. After we start the
  // SendPingThread, this thread falls back from running as the launcher process
  // to running as the browser main thread. Once this happens, it will be unsafe
  // to set up PoisonIOInterposer (since we have already spun up a background
  // thread).
  mozilla::SaveToEnv("MOZ_DISABLE_POISON_IO_INTERPOSER=1");

  // Capture aError and our module list into context for processing on another
  // thread.
  auto thdParam = mozilla::MakeUnique<PingThreadContext>(aError, aProcessType);

  // The ping does a lot of file I/O. Since we want this thread to continue
  // executing browser startup, we should gather that information on a
  // background thread.
  uintptr_t thdHandle =
      _beginthreadex(nullptr, 0, &SendPingThread, thdParam.get(),
                     STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
  if (!thdHandle) {
    return false;
  }

  // We have handed off thdParam to the background thread
  mozilla::Unused << thdParam.release();

  ::CloseHandle(reinterpret_cast<HANDLE>(thdHandle));
  return true;
#else
  return false;
#endif
}

namespace mozilla {

void HandleLauncherError(const LauncherError& aError,
                         const char* aProcessType) {
#if defined(MOZ_LAUNCHER_PROCESS)
  LauncherRegistryInfo regInfo;
  Unused << regInfo.DisableDueToFailure();
#endif  // defined(MOZ_LAUNCHER_PROCESS)

  if (!SendPing(aError, aProcessType)) {
    // couldn't (or shouldn't) send telemetry; fall back to event log
    PostErrorToLog(aError);
  }
}

void SetLauncherErrorAppData(const StaticXREAppData& aAppData) {
  gAppData = &aAppData;
}

void SetLauncherErrorForceEventLog() { gForceEventLog = true; }

}  // namespace mozilla
