chromium/chrome/browser/ash/file_manager/volume.cc

// Copyright 2013 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/file_manager/volume.h"

#include <string_view>

#include "ash/constants/ash_features.h"
#include "base/strings/strcat.h"
#include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h"
#include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"

namespace file_manager {
namespace {

using l10n_util::GetStringUTF8;
const char kMtpVolumeIdPrefix[] = "mtp:";

VolumeType MountTypeToVolumeType(ash::MountType type) {
  switch (type) {
    case ash::MountType::kInvalid:
      // A zip mount with an invalid path will return type kInvalid. We can use
      // a default VolumeType in this case.
      return VOLUME_TYPE_DOWNLOADS_DIRECTORY;
    case ash::MountType::kDevice:
      return VOLUME_TYPE_REMOVABLE_DISK_PARTITION;
    case ash::MountType::kArchive:
      return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE;
    case ash::MountType::kNetworkStorage:
      // Network storage mounts are handled by their mounters so
      // MountType::kNetworkStorage should never need to be handled
      // here.
      break;
  }

  NOTREACHED_IN_MIGRATION();
  return VOLUME_TYPE_DOWNLOADS_DIRECTORY;
}

// Returns a string representation of the given volume type.
std::string_view VolumeTypeToString(const VolumeType type) {
  switch (type) {
    case VOLUME_TYPE_GOOGLE_DRIVE:
      return "drive";
    case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
      return "downloads";
    case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
      return "removable";
    case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
      return "archive";
    case VOLUME_TYPE_PROVIDED:
      return "provided";
    case VOLUME_TYPE_MTP:
      return "mtp";
    case VOLUME_TYPE_MEDIA_VIEW:
      return "media_view";
    case VOLUME_TYPE_ANDROID_FILES:
      return "android_files";
    case VOLUME_TYPE_DOCUMENTS_PROVIDER:
      return "documents_provider";
    case VOLUME_TYPE_TESTING:
      return "testing";
    case VOLUME_TYPE_CROSTINI:
      return "crostini";
    case VOLUME_TYPE_SMB:
      return "smb";
    case VOLUME_TYPE_SYSTEM_INTERNAL:
      return "system_internal";
    case VOLUME_TYPE_GUEST_OS:
      return "guest_os";
    case NUM_VOLUME_TYPE:
      break;
  }

  NOTREACHED_IN_MIGRATION()
      << "Unexpected VolumeType value "
      << static_cast<std::underlying_type_t<VolumeType>>(type);
  return "";
}

// Generates a unique volume ID for the given volume info.
std::string GenerateVolumeId(const Volume& volume) {
  // For the same volume type, base names are unique, as mount points are
  // flat for the same volume type.
  return base::StrCat({VolumeTypeToString(volume.type()), ":",
                       volume.mount_path().BaseName().AsUTF8Unsafe()});
}

// Returns the localized label for a given media view.
std::string MediaViewRootIdToLabel(std::string_view root_id) {
  if (root_id == arc::kAudioRootId) {
    return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_AUDIO_ROOT_LABEL);
  }

  if (root_id == arc::kImagesRootId) {
    return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_IMAGES_ROOT_LABEL);
  }

  if (root_id == arc::kVideosRootId) {
    return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_VIDEOS_ROOT_LABEL);
  }

  if (root_id == arc::kDocumentsRootId) {
    return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_DOCUMENTS_ROOT_LABEL);
  }

  NOTREACHED_IN_MIGRATION() << "Unexpected root ID: " << root_id;
  return "";
}

}  // namespace

std::ostream& operator<<(std::ostream& out, const VolumeType type) {
  return out << VolumeTypeToString(type);
}

Volume::Volume() = default;
Volume::~Volume() = default;

// static
std::unique_ptr<Volume> Volume::CreateForDrive(base::FilePath drive_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_GOOGLE_DRIVE;
  volume->source_path_ = drive_path;
  volume->source_ = SOURCE_NETWORK;
  volume->mount_path_ = std::move(drive_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ = GetStringUTF8(IDS_FILE_BROWSER_DRIVE_DIRECTORY_LABEL);
  volume->watchable_ = true;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForDownloads(
    base::FilePath downloads_path,
    base::FilePath optional_fusebox_path,
    const char* optional_fusebox_volume_label,
    bool read_only) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_DOWNLOADS_DIRECTORY;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = std::move(downloads_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ = GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL);
  volume->watchable_ = true;
  volume->is_read_only_ = read_only;

  if (!optional_fusebox_path.empty()) {
    // Leaving the type_ as VOLUME_TYPE_DOWNLOADS_DIRECTORY means that, for
    // some unknown reason, it doesn't show up in the CrOS Files app. Use a
    // different but arbitrary type instead.
    //
    // It doesn't need to be well polished. It's just for debugging FuseBox. We
    // wouldn't normally need a FuseBox wrapper (exposing to the kernel-level
    // file system) for something like Downloads that's typically on local disk
    // (and hence already on the kernel-level file system).
    volume->type_ = VOLUME_TYPE_MTP;

    volume->file_system_type_ = util::kFuseBox;
    volume->mount_path_ = std::move(optional_fusebox_path);
    volume->volume_id_ =
        base::StrCat({util::kFuseBox, std::move(volume->volume_id_)});
    if (optional_fusebox_volume_label) {
      volume->volume_label_ = optional_fusebox_volume_label;
    }
  }

  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForRemovable(
    const ash::disks::DiskMountManager::MountPoint& mount_point,
    const ash::disks::Disk* disk) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = MountTypeToVolumeType(mount_point.mount_type);
  volume->source_path_ = base::FilePath(mount_point.source_path);
  volume->source_ = mount_point.mount_type == ash::MountType::kArchive
                        ? SOURCE_FILE
                        : SOURCE_DEVICE;
  volume->mount_path_ = base::FilePath(mount_point.mount_path);
  volume->mount_condition_ = mount_point.mount_error;

  if (disk) {
    volume->file_system_type_ = disk->file_system_type();
    volume->volume_label_ = disk->device_label();
    volume->device_type_ = disk->device_type();
    volume->storage_device_path_ = base::FilePath(disk->storage_device_path());
    volume->is_parent_ = disk->is_parent();
    volume->is_read_only_ = disk->is_read_only();
    volume->is_read_only_removable_device_ = disk->is_read_only_hardware();
    volume->has_media_ = disk->has_media();
    volume->drive_label_ = disk->drive_label();
  } else {
    volume->volume_label_ = volume->mount_path().BaseName().AsUTF8Unsafe();
    volume->is_read_only_ =
        (mount_point.mount_type == ash::MountType::kArchive);
  }
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->watchable_ = true;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForProvidedFileSystem(
    const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
    MountContext mount_context,
    base::FilePath optional_fusebox_path) {
  std::unique_ptr<Volume> volume(new Volume());

  switch (file_system_info.source()) {
    case extensions::SOURCE_FILE:
      volume->source_ = SOURCE_FILE;
      break;
    case extensions::SOURCE_DEVICE:
      volume->source_ = SOURCE_DEVICE;
      break;
    case extensions::SOURCE_NETWORK:
      volume->source_ = SOURCE_NETWORK;
      break;
  }

  volume->volume_label_ = file_system_info.display_name();
  volume->type_ = VOLUME_TYPE_PROVIDED;
  volume->mount_path_ = file_system_info.mount_path();
  volume->mount_context_ = mount_context;

  volume->is_parent_ = true;
  volume->is_read_only_ = !file_system_info.writable();
  volume->configurable_ = file_system_info.configurable();
  volume->watchable_ = file_system_info.watchable();
  volume->icon_set_ = file_system_info.icon_set();

  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->file_system_id_ = file_system_info.file_system_id();
  volume->provider_id_ = file_system_info.provider_id();

  if (!optional_fusebox_path.empty()) {
    volume->file_system_type_ = util::kFuseBox;
    if (ash::features::IsFileManagerFuseBoxDebugEnabled()) {
      volume->volume_label_.insert(0, "fusebox ");
    }
    volume->mount_path_ = std::move(optional_fusebox_path);
    // Even though the underlying FSP may support watchers, fusebox needs
    // to implement watchers in order to match the capability of the FSP.
    // TODO(crbug.com/1353673): Add watcher support to fusebox.
    volume->watchable_ = false;
    // "fusebox" prefix the original FSP volume id.
    volume->volume_id_ =
        base::StrCat({util::kFuseBox, GenerateVolumeId(*volume)});
  }

  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForMTP(base::FilePath mount_path,
                                             std::string label,
                                             bool read_only,
                                             bool use_fusebox) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_MTP;
  volume->mount_path_ = mount_path;
  volume->is_parent_ = true;
  volume->is_read_only_ = read_only;
  volume->volume_id_ = base::StrCat({kMtpVolumeIdPrefix, label});
  volume->volume_label_ = std::move(label);
  volume->source_path_ = std::move(mount_path);
  volume->source_ = SOURCE_DEVICE;
  volume->device_type_ = ash::DeviceType::kMobile;

  // MTP does have watcher support via WatcherManager but it doesn't
  // seem to work (perhaps something missing in mtpd).
  volume->watchable_ = false;

  if (use_fusebox) {
    volume->file_system_type_ = util::kFuseBox;
    volume->volume_id_ =
        base::StrCat({util::kFuseBox, std::move(volume->volume_id_)});
    if (ash::features::IsFileManagerFuseBoxDebugEnabled()) {
      volume->volume_label_.insert(0, "fusebox ");
    }
  }

  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForMediaView(const std::string& root_id) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_MEDIA_VIEW;
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = arc::GetDocumentsProviderMountPath(
      arc::kMediaDocumentsProviderAuthority, root_id);
  volume->volume_label_ = MediaViewRootIdToLabel(root_id);
  volume->is_read_only_ = false;
  volume->watchable_ = false;
  volume->volume_id_ = arc::GetMediaViewVolumeId(root_id);
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForSshfsCrostini(
    base::FilePath sshfs_mount_path,
    base::FilePath remote_mount_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_CROSTINI;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = std::move(sshfs_mount_path);
  volume->remote_mount_path_ = std::move(remote_mount_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ =
      GetStringUTF8(IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL);
  volume->watchable_ = true;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForSftpGuestOs(
    std::string display_name,
    base::FilePath sftp_mount_path,
    base::FilePath remote_mount_path,
    const guest_os::VmType vm_type) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = vm_type == guest_os::VmType::ARCVM ? VOLUME_TYPE_ANDROID_FILES
                                                     : VOLUME_TYPE_GUEST_OS;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = std::move(sftp_mount_path);
  volume->remote_mount_path_ = std::move(remote_mount_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ = std::move(display_name);
  volume->watchable_ = true;
  volume->vm_type_ = vm_type;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForAndroidFiles(
    base::FilePath mount_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_ANDROID_FILES;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = std::move(mount_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ =
      GetStringUTF8(IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL);
  volume->watchable_ = true;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForDocumentsProvider(
    const std::string& authority,
    const std::string& root_id,
    const std::string& title,
    const std::string& summary,
    const GURL& icon_url,
    bool read_only,
    const std::string& optional_fusebox_subdir) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_DOCUMENTS_PROVIDER;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = arc::GetDocumentsProviderMountPath(authority, root_id);
  volume->volume_label_ = title;
  volume->is_read_only_ = read_only;
  volume->watchable_ = false;
  volume->volume_id_ = arc::GetDocumentsProviderVolumeId(authority, root_id);
  if (!icon_url.is_empty()) {
    ash::file_system_provider::IconSet icon_set;
    icon_set.SetIcon(ash::file_system_provider::IconSet::IconSize::SIZE_32x32,
                     icon_url);
    volume->icon_set_ = icon_set;
  }

  if (!optional_fusebox_subdir.empty()) {
    volume->file_system_type_ = util::kFuseBox;
    volume->volume_id_.insert(0, util::kFuseBox);
    volume->mount_path_ =
        base::FilePath(util::kFuseBoxMediaPath).Append(optional_fusebox_subdir);
    if (ash::features::IsFileManagerFuseBoxDebugEnabled()) {
      volume->volume_label_.insert(0, "fusebox ");
    }
  }

  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForSmb(base::FilePath mount_point,
                                             std::string display_name) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_SMB;
  // Keep source_path empty.
  volume->source_ = SOURCE_NETWORK;
  volume->mount_path_ = std::move(mount_point);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->volume_label_ = std::move(display_name);
  volume->watchable_ = false;
  volume->is_read_only_ = false;
  return volume;
}

// ShareCache is not visible in the file manager and so this volume does not
// represent a real, user-visible Volume. However, shared files can be read
// through ImageLoader, which needs a Volume present to be able to read from the
// directory.
// static
std::unique_ptr<Volume> Volume::CreateForShareCache(base::FilePath mount_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = VOLUME_TYPE_SYSTEM_INTERNAL;
  // Keep source_path empty.
  volume->source_ = SOURCE_SYSTEM;
  volume->mount_path_ = std::move(mount_path);
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->watchable_ = false;
  volume->is_read_only_ = true;
  volume->hidden_ = true;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForTesting(base::FilePath path,
                                                 VolumeType volume_type,
                                                 ash::DeviceType device_type,
                                                 bool read_only,
                                                 base::FilePath device_path,
                                                 std::string drive_label,
                                                 std::string file_system_type,
                                                 bool hidden,
                                                 bool watchable) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->type_ = volume_type;
  volume->device_type_ = device_type;
  // Keep source_path empty.
  volume->source_ = SOURCE_DEVICE;
  volume->mount_path_ = std::move(path);
  volume->storage_device_path_ = std::move(device_path);
  volume->is_read_only_ = read_only;
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->drive_label_ = std::move(drive_label);
  volume->file_system_type_ = std::move(file_system_type);
  volume->hidden_ = hidden;
  volume->watchable_ = watchable;
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForTesting(base::FilePath device_path,
                                                 base::FilePath mount_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->storage_device_path_ = std::move(device_path);
  volume->mount_path_ = std::move(mount_path);
  return volume;
}

// static
std::unique_ptr<Volume> Volume::CreateForTesting(
    base::FilePath path,
    VolumeType volume_type,
    std::optional<guest_os::VmType> vm_type,
    base::FilePath source_path) {
  std::unique_ptr<Volume> volume(new Volume());
  volume->mount_path_ = std::move(path);
  volume->type_ = volume_type;
  volume->vm_type_ = vm_type;
  volume->volume_id_ = GenerateVolumeId(*volume);
  volume->source_path_ = std::move(source_path);
  return volume;
}

}  // namespace file_manager