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

#include <utility>

#include "ash/public/cpp/shelf_model.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/guest_os/guest_os_share_path.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_features.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.h"
#include "chrome/browser/ui/ash/shelf/app_window_base.h"
#include "chrome/browser/ui/ash/shelf/app_window_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_service.pb.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"

namespace plugin_vm {

namespace {

void DirExistsResult(
    const base::FilePath& dir,
    bool result,
    base::OnceCallback<void(const base::FilePath&, bool)> callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::move(callback).Run(dir, result);
}

void EnsureDirExists(
    base::FilePath dir,
    base::OnceCallback<void(const base::FilePath&, bool)> callback) {
  base::File::Error error = base::File::FILE_OK;
  bool result = base::CreateDirectoryAndGetError(dir, &error);
  if (!result) {
    LOG(ERROR) << "Failed to create PluginVm shared dir " << dir.value() << ": "
               << base::File::ErrorToString(error);
  }
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&DirExistsResult, dir, result, std::move(callback)));
}

base::FilePath GetDefaultSharedDir(Profile* profile) {
  return file_manager::util::GetMyFilesFolderForProfile(profile).Append(
      kPluginVmName);
}

void FocusAllPluginVmWindows() {
  ash::ShelfModel* shelf_model =
      ChromeShelfController::instance()->shelf_model();
  DCHECK(shelf_model);
  AppWindowShelfItemController* item_controller =
      shelf_model->GetAppWindowShelfItemController(
          ash::ShelfID(kPluginVmShelfAppId));
  if (!item_controller) {
    return;
  }
  for (AppWindowBase* app_window : item_controller->windows()) {
    app_window->Activate();
  }
}

// LaunchPluginVmApp will run before this and try to start Plugin VM.
void LaunchPluginVmAppImpl(Profile* profile,
                           std::string app_id,
                           std::vector<std::string> file_paths,
                           LaunchPluginVmAppCallback callback,
                           bool plugin_vm_is_running) {
  if (!plugin_vm_is_running) {
    return std::move(callback).Run(LaunchPluginVmAppResult::FAILED,
                                   "Plugin VM could not be started");
  }

  auto* registry_service =
      guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
  auto registration = registry_service->GetRegistration(app_id);
  if (!registration) {
    return std::move(callback).Run(
        LaunchPluginVmAppResult::FAILED,
        "LaunchPluginVmApp called with an unknown app_id: " + app_id);
  }

  vm_tools::cicerone::LaunchContainerApplicationRequest request;
  request.set_owner_id(ash::ProfileHelper::GetUserIdHashFromProfile(profile));
  request.set_vm_name(registration->VmName());
  request.set_container_name(registration->ContainerName());
  request.set_desktop_file_id(registration->DesktopFileId());
  base::ranges::move(file_paths, google::protobuf::RepeatedFieldBackInserter(
                                     request.mutable_files()));

  ash::CiceroneClient::Get()->LaunchContainerApplication(
      std::move(request),
      base::BindOnce(
          [](const std::string& app_id, LaunchPluginVmAppCallback callback,
             std::optional<
                 vm_tools::cicerone::LaunchContainerApplicationResponse>
                 response) {
            if (!response || !response->success()) {
              LOG(ERROR) << "Failed to launch application. "
                         << (response ? response->failure_reason()
                                      : "Empty response.");
              std::move(callback).Run(LaunchPluginVmAppResult::FAILED,
                                      "Failed to launch " + app_id);
              return;
            }

            FocusAllPluginVmWindows();

            std::move(callback).Run(LaunchPluginVmAppResult::SUCCESS, "");
          },
          std::move(app_id), std::move(callback)));
}

}  // namespace

base::FilePath ChromeOSBaseDirectory() {
  // Forward slashes are converted to backslash during path conversion.
  return base::FilePath("//ChromeOS");
}

void EnsureDefaultSharedDirExists(
    Profile* profile,
    base::OnceCallback<void(const base::FilePath&, bool)> callback) {
  base::ThreadPool::PostTask(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&EnsureDirExists, GetDefaultSharedDir(profile),
                     std::move(callback)));
}

void LaunchPluginVmApp(Profile* profile,
                       std::string app_id,
                       const std::vector<guest_os::LaunchArg>& args,
                       LaunchPluginVmAppCallback callback) {
  if (!plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile)) {
    return std::move(callback).Run(LaunchPluginVmAppResult::FAILED,
                                   "Plugin VM is not enabled for this profile");
  }

  auto* manager = PluginVmManagerFactory::GetForProfile(profile);

  if (!manager) {
    return std::move(callback).Run(LaunchPluginVmAppResult::FAILED,
                                   "Could not get PluginVmManager");
  }
  auto* share_path = guest_os::GuestOsSharePath::GetForProfile(profile);
  base::FilePath vm_mount = ChromeOSBaseDirectory();

  std::vector<std::string> launch_args;
  launch_args.reserve(args.size());
  for (const auto& arg : args) {
    if (absl::holds_alternative<std::string>(arg)) {
      launch_args.push_back(absl::get<std::string>(arg));
      continue;
    }
    const storage::FileSystemURL& url = absl::get<storage::FileSystemURL>(arg);
    base::FilePath file_path;
    // Validate paths in MyFiles/PvmDefault, or are already shared, and convert.
    bool shared = GetDefaultSharedDir(profile).IsParent(url.path()) ||
                  share_path->IsPathShared(kPluginVmName, url.path());
    if (!shared ||
        !file_manager::util::ConvertFileSystemURLToPathInsideVM(
            profile, url, vm_mount, /*map_crostini_home=*/false, &file_path)) {
      return std::move(callback).Run(
          LaunchPluginVmAppResult::FAILED_DIRECTORY_NOT_SHARED,
          "Only files in shared dirs are supported. Got: " + url.DebugString());
    }
    // Convert slashes: '/' => '\'.
    std::string result;
    base::ReplaceChars(file_path.value(), "/", "\\", &result);
    launch_args.push_back(std::move(result));
  }

  manager->LaunchPluginVm(
      base::BindOnce(&LaunchPluginVmAppImpl, profile, std::move(app_id),
                     std::move(launch_args), std::move(callback)));
}

}  // namespace plugin_vm