chromium/chrome/browser/ash/guest_os/guest_os_launcher.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 "chrome/browser/ash/guest_os/guest_os_launcher.h"

#include <sstream>

#include "base/logging.h"
#include "chrome/browser/ash/borealis/borealis_context.h"
#include "chrome/browser/ash/borealis/borealis_context_manager.h"
#include "chrome/browser/ash/borealis/borealis_service.h"
#include "chrome/browser/ash/bruschetta/bruschetta_launcher.h"
#include "chrome/browser/ash/bruschetta/bruschetta_service.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_simple_types.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_manager.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_manager_factory.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/ash/components/dbus/vm_launch/launch.pb.h"

namespace guest_os::launcher {

namespace {

ResponseType Success(std::string vm_name, std::string container_name) {
  vm_tools::launch::EnsureVmLaunchedResponse response;
  response.set_vm_name(std::move(vm_name));
  response.set_container_name(std::move(container_name));
  return ResponseType(std::move(response));
}

void LaunchBorealis(Profile* profile, LaunchCallback callback) {
  borealis::BorealisService::GetForProfile(profile)
      ->ContextManager()
      .StartBorealis(base::BindOnce(
          [](LaunchCallback callback,
             borealis::BorealisContextManager::ContextOrFailure
                 context_or_failure) {
            if (!context_or_failure.has_value()) {
              std::stringstream error_msg;
              error_msg << "Failed to launch ("
                        << static_cast<int>(context_or_failure.error().error())
                        << "): " << context_or_failure.error().description();
              std::move(callback).Run(base::unexpected(error_msg.str()));
              return;
            }
            std::move(callback).Run(Success(
                context_or_failure.value()->vm_name(), /*container_name=*/""));
          },
          std::move(callback)));
}

void LaunchCrostini(Profile* profile,
                    bool just_termina,
                    LaunchCallback callback) {
  crostini::CrostiniManager::RestartOptions options;
  options.start_vm_only = just_termina;
  auto container_id = crostini::DefaultContainerId();
  crostini::CrostiniManager::GetForProfile(profile)->RestartCrostiniWithOptions(
      container_id, std::move(options),
      base::BindOnce(
          [](std::string vm_name, std::string container_name,
             LaunchCallback callback, crostini::CrostiniResult result) {
            if (result != crostini::CrostiniResult::SUCCESS) {
              std::stringstream error_msg;
              error_msg << "Failed to launch: code="
                        << static_cast<int>(result);
              std::move(callback).Run(base::unexpected(error_msg.str()));
              return;
            }
            std::move(callback).Run(Success(vm_name, container_name));
          },
          container_id.vm_name, just_termina ? "" : container_id.container_name,
          std::move(callback)));
}

void LaunchPluginVm(Profile* profile, LaunchCallback callback) {
  plugin_vm::PluginVmManagerFactory::GetForProfile(profile)->LaunchPluginVm(
      base::BindOnce(
          [](LaunchCallback callback, bool success) {
            if (!success) {
              std::move(callback).Run(
                  base::unexpected("Failed to launch Plugin VM"));
              return;
            }
            std::move(callback).Run(
                Success(plugin_vm::kPluginVmName, /*container_name=*/""));
          },
          std::move(callback)));
}

void LaunchBruschetta(Profile* profile,
                      const std::string& name,
                      LaunchCallback callback) {
  auto* service = bruschetta::BruschettaService::GetForProfile(profile);
  auto launcher = service->GetLauncher(name);
  if (!launcher) {
    std::move(callback).Run(
        base::unexpected("No record found of a Bruschetta VM named " + name));
    return;
  }
  launcher->EnsureRunning(base::BindOnce(
      [](std::string name, LaunchCallback callback,
         bruschetta::BruschettaResult result) {
        if (result != bruschetta::BruschettaResult::kSuccess) {
          std::move(callback).Run(
              base::unexpected("Failed to launch Bruschetta"));
          return;
        }
        std::move(callback).Run(Success(name, /*container_name=*/"penguin"));
      },
      name, std::move(callback)));
}

}  // namespace

void EnsureLaunched(const vm_tools::launch::EnsureVmLaunchedRequest& request,
                    LaunchCallback response_callback) {
  if (request.launch_descriptors().empty()) {
    std::move(response_callback)
        .Run(base::unexpected("No launch_descriptors provided"));
    return;
  }

  Profile* profile = ProfileManager::GetPrimaryUserProfile();
  if (!profile || ash::ProfileHelper::GetUserIdHashFromProfile(profile) !=
                      request.owner_id()) {
    std::move(response_callback)
        .Run(base::unexpected(
            "Provided owner_id does not match the primary profile"));
    return;
  }

  // Descriptors are are an increasingly-specific list of identifiers for what
  // the user wants chrome to launch.
  //
  // E.g. ["crostini"] may refer to the default linux container, whereas
  // ["crostini", "foo"] could instead refer to a custom linux container called
  // "foo".
  const std::string& main_descriptor = request.launch_descriptors()[0];
  if (main_descriptor == "borealis") {
    LaunchBorealis(profile, std::move(response_callback));
  } else if (main_descriptor == "crostini") {
    LaunchCrostini(profile, /*just_termina=*/false,
                   std::move(response_callback));
  } else if (main_descriptor == "plugin_vm") {
    LaunchPluginVm(profile, std::move(response_callback));
  } else if (main_descriptor == "termina") {
    LaunchCrostini(profile, /*just_termina=*/true,
                   std::move(response_callback));
  } else if (main_descriptor == "bruschetta") {
    if (request.launch_descriptors().size() == 1) {
      std::move(response_callback)
          .Run(base::unexpected("Error: Bruschetta needs a name to launch"));
      return;
    }
    const std::string& name = request.launch_descriptors()[1];
    LaunchBruschetta(profile, name, std::move(response_callback));
  } else {
    std::move(response_callback)
        .Run(base::unexpected("Unknown descriptor: " + main_descriptor));
  }
}

void LaunchApplication(
    Profile* profile,
    const guest_os::GuestId& guest_id,
    guest_os::GuestOsRegistryService::Registration registration,
    int64_t display_id,
    const std::vector<std::string>& files,
    SuccessCallback callback) {
  if (registration.Terminal()) {
    // TODO(crbug.com/41395054): This could be improved by using garcon
    // DesktopFile::GenerateArgvWithFiles().
    std::vector<std::string> terminal_args = {
        registration.ExecutableFileName()};
    terminal_args.insert(terminal_args.end(), files.begin(), files.end());
    guest_os::LaunchTerminal(profile, display_id, guest_id,
                             /*cwd=*/std::string(), terminal_args);
    std::move(callback).Run(true, std::string());
    return;
  }

  vm_tools::cicerone::LaunchContainerApplicationRequest request;
  request.set_owner_id(crostini::CryptohomeIdForProfile(profile));
  request.set_vm_name(guest_id.vm_name);
  request.set_container_name(guest_id.container_name);
  request.set_desktop_file_id(registration.DesktopFileId());
  if (registration.IsScaled()) {
    request.set_display_scaling(
        vm_tools::cicerone::LaunchContainerApplicationRequest::SCALED);
  }
  base::ranges::copy(files, google::protobuf::RepeatedFieldBackInserter(
                                request.mutable_files()));

  const std::vector<vm_tools::cicerone::ContainerFeature> container_features =
      crostini::GetContainerFeatures();
  request.mutable_container_features()->Add(container_features.begin(),
                                            container_features.end());

  ash::CiceroneClient::Get()->LaunchContainerApplication(
      std::move(request),
      base::BindOnce(
          [](SuccessCallback callback,
             std::optional<
                 vm_tools::cicerone::LaunchContainerApplicationResponse>
                 response) {
            if (!response) {
              std::move(callback).Run(/*success=*/false,
                                      "Failed to launch application. Empty "
                                      "LaunchContainerApplicationResponse.");
              return;
            }
            std::move(callback).Run(response->success(),
                                    response->failure_reason());
          },
          std::move(callback)));
}

}  // namespace guest_os::launcher