chromium/chromeos/ash/services/libassistant/libassistant_loader_impl.cc

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

#include "chromeos/ash/services/libassistant/libassistant_loader_impl.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_enums.h"
#include "chromeos/ash/services/assistant/public/cpp/features.h"
#include "chromeos/ash/services/libassistant/constants.h"
#include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h"

namespace ash::libassistant {

namespace {

using InstallResult = assistant::LibassistantDlcInstallResult;
using LoadStatus = assistant::LibassistantDlcLoadStatus;

base::TaskTraits GetTaskTraits() {
  return {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
}

inline constexpr char kDlcInstallResultHistogram[] =
    "Assistant.Libassistant.DlcInstallResult";

inline constexpr char kDlcLoadStatusHistogram[] =
    "Assistant.Libassistant.DlcLoadStatus";

// The DLC ID of Libassistant.so, used to download and mount the library.
inline constexpr char kLibassistantDlcId[] = "assistant-dlc";

base::FilePath GetLibassisantPath(const std::string& root_path) {
  DCHECK(root_path == kLibAssistantDlcRootPath);
  base::FilePath libassistant_dlc_root =
      base::FilePath(root_path).AsEndingWithSeparator();
  return libassistant_dlc_root.Append(base::FilePath(kLibAssistantV2DlcPath));
}

void RecordLibassistantDlcInstallResult(
    const DlcserviceClient::InstallResult& result) {
  InstallResult install_result = InstallResult::kErrorInternal;
  if (result.error == dlcservice::kErrorNone) {
    install_result = InstallResult::kSuccess;
  }
  if (result.error == dlcservice::kErrorInternal) {
    install_result = InstallResult::kErrorInternal;
  }
  if (result.error == dlcservice::kErrorBusy) {
    install_result = InstallResult::kErrorBusy;
  }
  if (result.error == dlcservice::kErrorNeedReboot) {
    install_result = InstallResult::kErrorNeedReboot;
  }
  if (result.error == dlcservice::kErrorInvalidDlc) {
    install_result = InstallResult::kErrorInvalidDlc;
  }
  if (result.error == dlcservice::kErrorAllocation) {
    install_result = InstallResult::kErrorAllocation;
  }
  if (result.error == dlcservice::kErrorNoImageFound) {
    install_result = InstallResult::kErrorNoImageFound;
  }
  base::UmaHistogramEnumeration(kDlcInstallResultHistogram, install_result);
}

void RecordLibassistantDlcLoadStatus(const LoadStatus& status) {
  base::UmaHistogramEnumeration(kDlcLoadStatusHistogram, status);
}

}  // namespace

void LibassistantLoaderImpl::Load(LoadCallback callback) {
  if (entry_point_) {
    std::move(callback).Run(/*success=*/true);
    return;
  }

  InstallDlc(std::move(callback));
}

void LibassistantLoaderImpl::LoadBlocking(const std::string& root_path) {
  // We will load the libassistant before the sandbox initializes.
  // Since we are not in the main thread, we can call the blocking method.
  DCHECK(!entry_point_);

  // If the gRPC socket files exist, libassistant gRPC server could not start
  // because the binding to the new socket files will fail, with error message
  // that the files already exist.
  const bool is_chromeos_device = base::SysInfo::IsRunningOnChromeOS();
  DVLOG(3) << "Clean up temporary libassistant directory.";
  auto socket_path = base::FilePath(kLibAssistantSocketPath);
  base::DeletePathRecursively(socket_path);
  if (!is_chromeos_device) {
    // Make sure the directory exists. On a real device, this directory will be
    // created on the OS side when Chrome starts.
    CHECK(base::CreateDirectory(socket_path));
  }

  base::FilePath path = GetLibassisantPath(root_path);
  base::ScopedNativeLibrary library = base::ScopedNativeLibrary(path);
  OnLibraryLoaded(std::move(library));
}

EntryPoint* LibassistantLoaderImpl::GetEntryPoint() {
  DCHECK(entry_point_);
  return entry_point_.get();
}

LibassistantLoaderImpl::LibassistantLoaderImpl()
    : task_runner_(
          base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits())) {}

LibassistantLoaderImpl::~LibassistantLoaderImpl() = default;

void LibassistantLoaderImpl::InstallDlc(LoadCallback callback) {
  callback_ = std::move(callback);

  // Install libassistant.so from DLC.
  auto* client = DlcserviceClient::Get();
  if (!client) {
    DVLOG(1) << "DlcService client is not available";
    RunCallback(/*success=*/false);
    return;
  }

  DVLOG(3) << "Installing libassistant.so from DLC";
  dlcservice::InstallRequest install_request;
  install_request.set_id(kLibassistantDlcId);
  client->Install(install_request,
                  base::BindOnce(&LibassistantLoaderImpl::OnInstallDlcComplete,
                                 weak_factory_.GetWeakPtr()),
                  /*ProgressCallback=*/base::DoNothing());
}

void LibassistantLoaderImpl::OnInstallDlcComplete(
    const DlcserviceClient::InstallResult& result) {
  RecordLibassistantDlcInstallResult(result);

  if (result.error != dlcservice::kErrorNone) {
    DVLOG(1) << "Failed to install libassistant.so from DLC: " << result.error;
    RunCallback(/*success=*/false);
    return;
  }

  if (assistant::features::IsLibAssistantSandboxEnabled()) {
    // Will load the library later in the utility process.
    RunCallback(/*success=*/true);
    return;
  }

  // `ScopedNativeLibrary` will call `LoadNativeLibraryWithOptions()`, which is
  // a blocking call. We need to send to a background thread to load it.
  base::FilePath path = GetLibassisantPath(result.root_path);
  DVLOG(3) << "Loading libassistant.so DLC from: " << path;
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const base::FilePath& path) {
            const bool is_chromeos_device =
                base::SysInfo::IsRunningOnChromeOS();
            if (!is_chromeos_device) {
              // Make sure the directory exists. On a real device, this
              // directory will be created on the OS side when Chrome starts.
              auto socket_path = base::FilePath(kLibAssistantSocketPath);
              CHECK(base::CreateDirectory(socket_path));
            }
            return base::ScopedNativeLibrary(path);
          },
          path),
      base::BindOnce(&LibassistantLoaderImpl::OnLibraryLoaded,
                     weak_factory_.GetWeakPtr()));
}

void LibassistantLoaderImpl::OnLibraryLoaded(
    base::ScopedNativeLibrary library) {
  if (!library.is_valid()) {
    DVLOG(1) << "Failed to load libassistant.so DLC,  error: "
             << library.GetError()->ToString();
    RecordLibassistantDlcLoadStatus(LoadStatus::kNotLoaded);
    RunCallback(/*success=*/false);
    return;
  }

  // Call exported function in libassistant.so.
  NewLibassistantEntrypointFn entrypoint =
      reinterpret_cast<NewLibassistantEntrypointFn>(
          library.GetFunctionPointer(kNewLibassistantEntrypointFnName));
  C_API_LibassistantEntrypoint* c_entrypoint = entrypoint(0);
  auto* entry_point =
      assistant_client::internal_api::LibassistantEntrypointFromC(c_entrypoint);

  DVLOG(3) << "Loaded libassistant.so.";
  RecordLibassistantDlcLoadStatus(LoadStatus::kLoaded);

  dlc_library_ = std::move(library);
  entry_point_ = base::WrapUnique(entry_point);
  RunCallback(/*success=*/true);
}

void LibassistantLoaderImpl::RunCallback(bool success) {
  if (callback_) {
    std::move(callback_).Run(success);
  }
}

// static
LibassistantLoaderImpl* LibassistantLoaderImpl::GetInstance() {
  // TODO(b/242098785): Investigate if we could remove NoDestructor.
  static base::NoDestructor<LibassistantLoaderImpl> instance;
  return instance.get();
}

// static
void LibassistantLoader::Load(LoadCallback callback) {
  LibassistantLoaderImpl::GetInstance()->Load(std::move(callback));
}

}  // namespace ash::libassistant