chromium/chromecast/system/reboot/reboot_fuchsia.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/hardware/power/statecontrol/cpp/fidl.h>
#include <fuchsia/recovery/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <zircon/status.h>
#include <zircon/types.h>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/no_destructor.h"
#include "chromecast/public/reboot_shlib.h"
#include "chromecast/system/reboot/fuchsia_component_restart_reason.h"
#include "chromecast/system/reboot/reboot_fuchsia.h"
#include "chromecast/system/reboot/reboot_util.h"

using fuchsia::feedback::LastReboot;
using fuchsia::feedback::LastRebootInfoProviderSyncPtr;
using fuchsia::feedback::RebootReason;
using fuchsia::hardware::power::statecontrol::Admin_Reboot_Result;
using fuchsia::hardware::power::statecontrol::AdminPtr;
using fuchsia::recovery::FactoryResetPtr;
using StateControlRebootReason =
    fuchsia::hardware::power::statecontrol::RebootReason;

namespace chromecast {

namespace {
FuchsiaComponentRestartReason state_;

// If true, the next reboot should trigger a factory data reset.
bool fdr_on_reboot_ = false;
}

AdminPtr& GetAdminPtr() {
  static base::NoDestructor<AdminPtr> g_admin;
  return *g_admin;
}

LastRebootInfoProviderSyncPtr& GetLastRebootInfoProviderSyncPtr() {
  static base::NoDestructor<LastRebootInfoProviderSyncPtr> g_last_reboot_info;
  return *g_last_reboot_info;
}

fuchsia::recovery::FactoryResetPtr& GetFactoryResetPtr() {
  static base::NoDestructor<FactoryResetPtr> g_factory_reset;
  return *g_factory_reset;
}

void InitializeRebootShlib(const std::vector<std::string>& argv,
                           sys::ServiceDirectory* incoming_directory) {
  incoming_directory->Connect(GetAdminPtr().NewRequest());
  incoming_directory->Connect(GetLastRebootInfoProviderSyncPtr().NewRequest());
  incoming_directory->Connect(GetFactoryResetPtr().NewRequest());
  GetAdminPtr().set_error_handler([](zx_status_t status) {
    ZX_LOG(ERROR, status) << "AdminPtr disconnected";
  });
  GetFactoryResetPtr().set_error_handler([](zx_status_t status) {
    ZX_LOG(ERROR, status) << "FactoryResetPtr disconnected";
  });
  InitializeRestartCheck();
}

base::FilePath InitializeFlagFileDirForTesting(const base::FilePath sub) {
  return state_.SetFlagFileDirForTesting(sub);
}

void InitializeRestartCheck() {
  state_.ResetRestartCheck();
}

// RebootShlib implementation:

// static
void RebootShlib::Initialize(const std::vector<std::string>& argv) {
  InitializeRebootShlib(argv, base::ComponentContextForProcess()->svc().get());
}

// static
void RebootShlib::Finalize() {}

// static
bool RebootShlib::IsSupported() {
  return true;
}

// static
bool RebootShlib::IsRebootSourceSupported(
    RebootShlib::RebootSource /* reboot_source */) {
  return true;
}

// static
bool RebootShlib::RebootNow(RebootSource reboot_source) {
  if (fdr_on_reboot_) {
    fdr_on_reboot_ = false;

    // Intentionally using async Ptr to avoid deadlock
    // Otherwise caller is blocked, and if caller needs to be notified
    // as well, it will go into a deadlock state.
    GetFactoryResetPtr()->Reset([](zx_status_t status) {
      if (status != ZX_OK) {
        ZX_LOG(ERROR, status) << "Failed to trigger FDR";
      }
    });

    // FDR will perform its own reboot, return immediately.
    return true;
  }

  StateControlRebootReason reason;
  switch (reboot_source) {
    case RebootSource::API:
      reason = StateControlRebootReason::USER_REQUEST;
      break;
    case RebootSource::OTA:
      // We expect OTAs to be initiated by the platform via the
      // fuchsia.hardware.power.statecontrol/Admin FIDL service. In case
      // non-platform code wants to initiate OTAs too, we also support it here.
      reason = StateControlRebootReason::SYSTEM_UPDATE;
      break;
    case RebootSource::OVERHEAT:
      reason = StateControlRebootReason::HIGH_TEMPERATURE;
      break;
    default:
      reason = StateControlRebootReason::USER_REQUEST;
      break;
  }

  // Intentionally using async Ptr to avoid deadlock
  // Otherwise caller is blocked, and if caller needs to be notified
  // as well, it will go into a deadlock state.
  GetAdminPtr()->Reboot(reason, [](Admin_Reboot_Result out_result) {
    if (out_result.is_err()) {
      LOG(ERROR) << "Failed to reboot after requested: "
                 << zx_status_get_string(out_result.err());
    }
  });
  return true;
}

// static
bool RebootShlib::IsFdrForNextRebootSupported() {
  return true;
}

// static
void RebootShlib::SetFdrForNextReboot() {
  fdr_on_reboot_ = true;
}

// static
bool RebootShlib::IsOtaForNextRebootSupported() {
  return false;
}

// static
void RebootShlib::SetOtaForNextReboot() {}

// static
bool RebootShlib::IsClearOtaForNextRebootSupported() {
  return false;
}

// static
void RebootShlib::ClearOtaForNextReboot() {}

// RebootUtil implementation:

// static
void RebootUtil::Initialize(const std::vector<std::string>& argv) {
  RebootShlib::Initialize(argv);
}

// static
void RebootUtil::Finalize() {
  RebootShlib::Finalize();
  state_.RegisterTeardown();
}

// static
RebootShlib::RebootSource RebootUtil::GetLastRebootSource() {
  RebootShlib::RebootSource last_restart;
  if (state_.GetRestartReason(&last_restart))
    return last_restart;

  LastReboot last_reboot;
  zx_status_t status = GetLastRebootInfoProviderSyncPtr()->Get(&last_reboot);
  if (status != ZX_OK || last_reboot.IsEmpty() || !last_reboot.has_graceful()) {
    ZX_LOG(ERROR, status) << "Failed to get last reboot reason";
    return RebootShlib::RebootSource::UNKNOWN;
  }

  if (!last_reboot.has_reason()) {
    return last_reboot.graceful() ? RebootShlib::RebootSource::SW_OTHER
                                  : RebootShlib::RebootSource::FORCED;
  }

  switch (last_reboot.reason()) {
    case RebootReason::COLD:
    case RebootReason::BRIEF_POWER_LOSS:
    case RebootReason::BROWNOUT:
    case RebootReason::KERNEL_PANIC:
      return RebootShlib::RebootSource::FORCED;
    case RebootReason::SYSTEM_OUT_OF_MEMORY:
      return RebootShlib::RebootSource::REPEATED_OOM;
    case RebootReason::HARDWARE_WATCHDOG_TIMEOUT:
      return RebootShlib::RebootSource::HW_WATCHDOG;
    case RebootReason::SOFTWARE_WATCHDOG_TIMEOUT:
      return RebootShlib::RebootSource::WATCHDOG;
    case RebootReason::USER_REQUEST:
      return RebootShlib::RebootSource::API;
    case RebootReason::SYSTEM_UPDATE:
    case RebootReason::RETRY_SYSTEM_UPDATE:
    case RebootReason::ZBI_SWAP:
      return RebootShlib::RebootSource::OTA;
    case RebootReason::HIGH_TEMPERATURE:
      return RebootShlib::RebootSource::OVERHEAT;
    case RebootReason::SESSION_FAILURE:
      return RebootShlib::RebootSource::SW_OTHER;
    default:
      return last_reboot.graceful() ? RebootShlib::RebootSource::SW_OTHER
                                    : RebootShlib::RebootSource::FORCED;
  }
}

// static
bool RebootUtil::SetNextRebootSource(
    RebootShlib::RebootSource /* reboot_source */) {
  return false;
}

}  // namespace chromecast