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

#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/ash/crostini/crostini_features.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_types.mojom-shared.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
#include "chrome/browser/extensions/api/terminal/startup_status.h"
#include "chrome/browser/ui/views/crostini/crostini_recovery_view.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"

namespace crostini {
using mojom::InstallerState;

namespace {
// Counts from chrome/browser/ash/crostini/crostini_types.mojom.
const int kMaxStage = static_cast<int>(InstallerState::kConfigureContainer) + 1;
}  // namespace

// Displays startup status to the crostini terminal.
class CrostiniStartupStatus : public crostini::CrostiniManager::RestartObserver,
                              public extensions::StartupStatus {
 public:
  explicit CrostiniStartupStatus(
      std::unique_ptr<extensions::StartupStatusPrinter> printer)
      : StartupStatus(std::move(printer), kMaxStage) {}
  ~CrostiniStartupStatus() override = default;

  // crostini::CrostiniManager::RestartObserver
  void OnStageStarted(crostini::mojom::InstallerState stage) override {
    int stage_index = static_cast<int>(stage);
    static base::NoDestructor<base::flat_map<InstallerState, std::string>>
        kStartStrings({
            {InstallerState::kStart,
             l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_START)},
            {InstallerState::kInstallImageLoader,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_INSTALL_IMAGE_LOADER)},
            {InstallerState::kCreateDiskImage,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_CREATE_DISK_IMAGE)},
            {InstallerState::kStartTerminaVm,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_START_TERMINA_VM)},
            {InstallerState::kStartLxd,
             l10n_util::GetStringUTF8(IDS_CROSTINI_TERMINAL_STATUS_START_LXD)},
            {InstallerState::kCreateContainer,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_CREATE_CONTAINER)},
            {InstallerState::kSetupContainer,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_SETUP_CONTAINER)},
            {InstallerState::kStartContainer,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_START_CONTAINER)},
            {InstallerState::kConfigureContainer,
             l10n_util::GetStringUTF8(
                 IDS_CROSTINI_TERMINAL_STATUS_CONFIGURE_CONTAINER)},
        });
    const std::string& stage_string = (*kStartStrings)[stage];
    // Ensure we have a valid string for each stage.
    DCHECK(!stage_string.empty());
    printer()->PrintStage(stage_index, stage_string);
  }
};

CrostiniTerminalProvider::CrostiniTerminalProvider(
    Profile* profile,
    guest_os::GuestId container_id)
    : profile_(profile), container_id_(container_id) {}
CrostiniTerminalProvider::~CrostiniTerminalProvider() = default;

std::string CrostiniTerminalProvider::Label() {
  return crostini::FormatForUi(container_id_);
}

guest_os::GuestId CrostiniTerminalProvider::GuestId() {
  return container_id_;
}

bool CrostiniTerminalProvider::RecoveryRequired(int64_t display_id) {
  auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile_);
  if (crostini_manager->IsUncleanStartup()) {
    ShowCrostiniRecoveryView(profile_, crostini::CrostiniUISurface::kAppList,
                             guest_os::kTerminalSystemAppId, display_id, {},
                             base::DoNothing());
    return true;
  }
  return false;
}

bool CrostiniTerminalProvider::AllowedByPolicy() {
  return CrostiniFeatures::Get()->IsAllowedNow(profile_);
}

std::string CrostiniTerminalProvider::PrepareCwd(storage::FileSystemURL url) {
  std::string cwd;
  CrostiniManager::RestartOptions options;
  auto* share_path = guest_os::GuestOsSharePath::GetForProfile(profile_);
  base::FilePath path;
  if (file_manager::util::ConvertFileSystemURLToPathInsideCrostini(
          profile_, url, &path)) {
    cwd = path.value();
    const auto& fs_id = url.mount_filesystem_id();
    auto mount_name =
        // TODO(b/217469540): Currently the default Crostini container gets
        // mounted in a different location to other Guest OS mounts, as we get
        // consistent file sharing across Guest OSs we can remove this special
        // case.
        (container_id_ == DefaultContainerId())
            ? file_manager::util::GetCrostiniMountPointName(profile_)
            : file_manager::util::GetGuestOsMountPointName(profile_,
                                                           container_id_);
    if (fs_id != mount_name &&
        !share_path->IsPathShared(container_id_.vm_name, url.path())) {
      // Path isn't already shared, so share it.
      options.share_paths.push_back(url.path());
    }
  } else {
    LOG(WARNING) << "Failed to parse: " << path << ". Not setting terminal cwd";
    return "";
  }
  // This completes async, but we don't wait for it since the terminal itself
  // also calls RestartCrostini and that'll get serialised, ensuring that this
  // call has completed before the share gets used.
  CrostiniManager::GetForProfile(profile_)->RestartCrostiniWithOptions(
      container_id_, std::move(options), base::DoNothing());
  return cwd;
}

std::unique_ptr<extensions::StartupStatus>
CrostiniTerminalProvider::CreateStartupStatus(
    std::unique_ptr<extensions::StartupStatusPrinter> printer) {
  return std::make_unique<CrostiniStartupStatus>(std::move(printer));
}

void CrostiniTerminalProvider::EnsureRunning(
    extensions::StartupStatus* startup_status,
    base::OnceCallback<void(bool success, std::string failure_reason)>
        callback) {
  CrostiniManager::GetForProfile(profile_)->RestartCrostini(
      container_id_,
      base::BindOnce(
          [](base::OnceCallback<void(bool, std::string)> callback,
             CrostiniResult result) {
            if (result == CrostiniResult::SUCCESS) {
              std::move(callback).Run(true, "");
            } else {
              crostini::RecordAppLaunchResultHistogram(
                  crostini::CrostiniAppLaunchAppType::kTerminal, result);
              std::move(callback).Run(
                  false,
                  base::StringPrintf(
                      "Error starting crostini for terminal: %d (%s)",
                      static_cast<int>(result), CrostiniResultString(result)));
            }
          },
          std::move(callback)),
      // Downcast is safe because terminal_private_api.cc will only call us with
      // the startup status we created in CreateStartupStatus.
      static_cast<CrostiniStartupStatus*>(startup_status));
}

}  // namespace crostini