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

#include <memory>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/infra/cached_callback.h"
#include "chrome/browser/ash/guest_os/public/types.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "storage/browser/file_system/external_mount_points.h"

namespace guest_os {

// An RAII-style class controlling the lifetime of the SFTP volume. Will add the
// volume on creation and remove it on destruction.
class ScopedVolume {
 public:
  explicit ScopedVolume(
      Profile* profile,
      std::string display_name,
      std::string mount_label,
      base::FilePath remote_path,
      const ash::disks::DiskMountManager::MountPoint& mount_info,
      VmType vm_type)
      : profile_(profile),
        mount_label_(std::move(mount_label)),
        vm_type_(vm_type) {
    base::FilePath mount_path = base::FilePath(mount_info.mount_path);
    if (!storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
            mount_label_, storage::kFileSystemTypeLocal,
            storage::FileSystemMountOption(), mount_path)) {
      // We don't revoke the filesystem on unmount and this call fails if a
      // filesystem of the same name already exists, so ignore errors.
      // TODO(crbug/1293229): This follows the logic of existing code, but we
      // can probably change it to revoke the filesystem on unmount.
    }
    auto* vmgr = file_manager::VolumeManager::Get(profile_);
    if (vmgr) {
      // vmgr is null in unit tests.
      vmgr->AddSftpGuestOsVolume(display_name, mount_path, remote_path,
                                 vm_type_);
    }
  }

  ~ScopedVolume() {
    if (profile_->ShutdownStarted()) {
      // We're shutting down or have shut down, but because we're not a keyed
      // service we don't get two-phase shutdown, we just can't call anything.
      // Either the whole system is shutting down (in which case everything
      // gets undone anyway) or it's just the browser (in which case it's
      // basically the same as a browser crash which we also need to handle).
      // So do nothing.
      return;
    }

    auto* vmgr = file_manager::VolumeManager::Get(profile_.get());
    if (vmgr) {
      // vmgr is null in unit tests. Also, this calls disk_manager to unmount
      // for us (and we never unregister the filesystem) hence unmount doesn't
      // seem symmetric with mount.
      vmgr->RemoveSftpGuestOsVolume(
          file_manager::util::GetGuestOsMountDirectory(mount_label_), vm_type_,
          base::DoNothing());
    }
  }

  raw_ptr<Profile> profile_;
  std::string mount_label_;
  const VmType vm_type_;
};

class GuestOsMountProviderInner : public CachedCallback<ScopedVolume, bool> {
 public:
  explicit GuestOsMountProviderInner(
      Profile* profile,
      std::string display_name,
      guest_os::GuestId container_id,
      VmType vm_type,
      base::RepeatingCallback<void(GuestOsMountProvider::PrepareCallback)>
          prepare)
      : profile_(profile),
        display_name_(std::move(display_name)),
        container_id_(std::move(container_id)),
        vm_type_(vm_type),
        prepare_(std::move(prepare)) {
    // This profile should be the user's primary profile, not an incognito one.
    DCHECK(!profile->IsOffTheRecord());
  }

  // Mount.
  void Build(RealCallback callback) override {
    prepare_.Run(base::BindOnce(&GuestOsMountProviderInner::MountPath,
                                weak_ptr_factory_.GetWeakPtr(),
                                std::move(callback)));
  }

  void MountPath(RealCallback callback,
                 bool success,
                 int cid,
                 int port,
                 base::FilePath remote_path) {
    if (!success) {
      LOG(ERROR) << "Error mounting, failed to prepare VM";
      std::move(callback).Run(Failure(false));
      return;
    }
    mount_label_ =
        file_manager::util::GetGuestOsMountPointName(profile_, container_id_);
    auto* dmgr = ash::disks::DiskMountManager::GetInstance();

    // Call to sshfs to mount.
    std::string source_path = base::StringPrintf("sftp://%d:%d", cid, port);

    dmgr->MountPath(source_path, "", mount_label_, {},
                    ash::MountType::kNetworkStorage,
                    ash::MountAccessMode::kReadWrite,
                    base::BindOnce(&GuestOsMountProviderInner::OnMountEvent,
                                   weak_ptr_factory_.GetWeakPtr(),
                                   std::move(callback), remote_path));
  }

  void OnMountEvent(
      RealCallback callback,
      base::FilePath remote_path,
      ash::MountError error_code,
      const ash::disks::DiskMountManager::MountPoint& mount_info) {
    if (error_code != ash::MountError::kSuccess) {
      LOG(ERROR) << "Error mounting Guest OS container: error_code="
                 << error_code << ", source_path=" << mount_info.source_path
                 << ", mount_path=" << mount_info.mount_path
                 << ", mount_type=" << static_cast<int>(mount_info.mount_type)
                 << ", mount_error=" << mount_info.mount_error;
      std::move(callback).Run(Failure(false));
      return;
    }
    auto scoped_volume =
        std::make_unique<ScopedVolume>(profile_, display_name_, mount_label_,
                                       remote_path, mount_info, vm_type_);

    // CachedCallback magic keeps the scope alive until we're destroyed or it's
    // invalidated.
    std::move(callback).Run(RealResult(std::move(scoped_volume)));
  }

  raw_ptr<Profile> profile_;
  const std::string display_name_;
  const guest_os::GuestId container_id_;
  std::string mount_label_;
  const VmType vm_type_;
  // Callback to prepare the VM for mounting.
  const base::RepeatingCallback<void(GuestOsMountProvider::PrepareCallback)>
      prepare_;

  // Note: This should remain the last member so it'll be destroyed and
  // invalidate its weak pointers before any other members are destroyed.
  base::WeakPtrFactory<GuestOsMountProviderInner> weak_ptr_factory_{this};
};

void GuestOsMountProvider::Mount(base::OnceCallback<void(bool)> callback) {
  const bool local_files_allowed =
      policy::local_user_files::LocalUserFilesAllowed();

  // If SkyVaultV2 is enabled (GA version), block all VMs regardless of the
  // type.
  if (!local_files_allowed &&
      base::FeatureList::IsEnabled(features::kSkyVaultV2)) {
    LOG(ERROR) << "Error mounting Guest OS container with guest id="
               << this->GuestId() << ": local user files are disabled";
    std::move(callback).Run(false);
    return;
  }

  // If SkyVaultV2 is disabled (TT version), only block ARC.
  if (!local_files_allowed && vm_type() == VmType::ARCVM) {
    LOG(ERROR) << "Error mounting Guest OS container with guest id="
               << this->GuestId() << ": local user files are disabled";
    std::move(callback).Run(false);
    return;
  }

  if (!callback_) {
    callback_ = std::make_unique<GuestOsMountProviderInner>(
        profile(), DisplayName(), GuestId(), vm_type(),
        base::BindRepeating(&GuestOsMountProvider::Prepare,
                            weak_ptr_factory_.GetWeakPtr()));
  }
  callback_->Get(base::BindOnce(
      [](base::OnceCallback<void(bool)> callback,
         guest_os::GuestOsMountProviderInner::Result result) {
        std::move(callback).Run(result.has_value());
      },
      std::move(callback)));
}

void GuestOsMountProvider::Unmount() {
  callback_->Invalidate();
}

GuestOsMountProvider::GuestOsMountProvider() = default;
GuestOsMountProvider::~GuestOsMountProvider() = default;
}  // namespace guest_os