blob: 6b4956fe596f4372a50e125a864dd01aeea31aeb [file] [log] [blame]
// 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_manager.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_running_on_chromeos.h"
#include "chrome/browser/ash/arc/fileapi/arc_file_system_operation_runner.h"
#include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drive_integration_service_factory.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/volume.h"
#include "chrome/browser/ash/file_manager/volume_manager_observer.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/download/download_dir_util.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
#include "chromeos/ash/experiences/arc/arc_prefs.h"
#include "chromeos/ash/experiences/arc/session/arc_bridge_service.h"
#include "chromeos/ash/experiences/arc/session/arc_service_manager.h"
#include "chromeos/ash/experiences/arc/test/connection_holder_util.h"
#include "chromeos/ash/experiences/arc/test/fake_file_system_instance.h"
#include "chromeos/components/disks/disks_prefs.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/storage_monitor/storage_info.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/browser/extension_registry.h"
#include "services/device/public/mojom/mtp_storage_info.mojom.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace file_manager {
namespace {
using ::ash::MountError;
using ::ash::MountType;
using ::ash::disks::Disk;
using ::ash::disks::DiskMountManager;
using ::ash::disks::FakeDiskMountManager;
using base::FilePath;
using ::testing::UnorderedElementsAre;
std::vector<std::string> arc_volume_ids = {
arc::kImagesRootId, arc::kVideosRootId, arc::kAudioRootId,
arc::kDocumentsRootId, "android_files:0"};
const char kAllowlistedVendorId[] = "A123";
const char kAllowlistedProductId[] = "456B";
const policy::DeviceId kAllowlistedDeviceId{0xA123, 0x456B};
// Adds `kAllowlistedDeviceId` to ExternalStorageAllowlist.
void SetExternalStorageAllowlist(PrefService* pref_service) {
pref_service->SetList(
disks::prefs::kExternalStorageAllowlist,
base::Value::List().Append(kAllowlistedDeviceId.ToDict()));
}
std::unique_ptr<Disk> CreateAllowlistedDisk(const std::string& disk_path) {
return Disk::Builder()
.SetDevicePath(disk_path)
.SetVendorId(kAllowlistedVendorId)
.SetProductId(kAllowlistedProductId)
.SetHasMedia(true)
.Build();
}
device::mojom::MtpStorageInfoPtr CreateAllowlistedMtpStorageInfo(
std::string_view storage_name) {
auto mtp_storage_info = device::mojom::MtpStorageInfo::New();
mtp_storage_info->vendor_id = kAllowlistedDeviceId.vid;
mtp_storage_info->product_id = kAllowlistedDeviceId.pid;
mtp_storage_info->storage_name = storage_name;
return mtp_storage_info;
}
class LoggingObserver : public VolumeManagerObserver {
public:
class Event {
public:
enum EventType {
DISK_ADDED,
DISK_ADD_BLOCKED_BY_POLICY,
DISK_REMOVED,
DEVICE_ADDED,
DEVICE_REMOVED,
VOLUME_MOUNTED,
VOLUME_UNMOUNTED,
FORMAT_STARTED,
FORMAT_COMPLETED,
PARTITION_STARTED,
PARTITION_COMPLETED,
RENAME_STARTED,
RENAME_COMPLETED
};
EventType type() const { return type_.value(); }
std::string device_path() const { return device_path_.value(); }
std::string device_label() const { return device_label_.value(); }
std::string volume_id() const { return volume_id_.value(); }
bool mounting() const { return mounting_.value(); }
ash::MountError mount_error() const { return mount_error_.value(); }
bool success() const { return success_.value(); }
private:
friend class LoggingObserver;
std::optional<EventType> type_;
std::optional<std::string> device_path_;
std::optional<std::string> device_label_;
std::optional<std::string> volume_id_;
std::optional<bool> mounting_;
std::optional<ash::MountError> mount_error_;
std::optional<bool> success_;
};
LoggingObserver() = default;
LoggingObserver(const LoggingObserver&) = delete;
LoggingObserver& operator=(const LoggingObserver&) = delete;
~LoggingObserver() override = default;
const std::vector<Event>& events() const { return events_; }
// VolumeManagerObserver overrides.
void OnDiskAdded(const Disk& disk, bool mounting) override {
Event event;
event.type_ = Event::DISK_ADDED;
event.device_path_ = disk.device_path(); // Keep only device_path.
event.mounting_ = mounting;
events_.push_back(event);
}
void OnDiskAddBlockedByPolicy(const std::string& device_path) override {
Event event;
event.type_ = Event::DISK_ADD_BLOCKED_BY_POLICY;
event.device_path_ = device_path;
events_.push_back(event);
}
void OnDiskRemoved(const Disk& disk) override {
Event event;
event.type_ = Event::DISK_REMOVED;
event.device_path_ = disk.device_path(); // Keep only device_path.
events_.push_back(event);
}
void OnDeviceAdded(const std::string& device_path) override {
Event event;
event.type_ = Event::DEVICE_ADDED;
event.device_path_ = device_path;
events_.push_back(event);
}
void OnDeviceRemoved(const std::string& device_path) override {
Event event;
event.type_ = Event::DEVICE_REMOVED;
event.device_path_ = device_path;
events_.push_back(event);
}
void OnVolumeMounted(ash::MountError error_code,
const Volume& volume) override {
Event event;
event.type_ = Event::VOLUME_MOUNTED;
event.device_path_ = volume.source_path().AsUTF8Unsafe();
event.volume_id_ = volume.volume_id();
event.mount_error_ = error_code;
events_.push_back(event);
}
void OnVolumeUnmounted(ash::MountError error_code,
const Volume& volume) override {
Event event;
event.type_ = Event::VOLUME_UNMOUNTED;
event.device_path_ = volume.source_path().AsUTF8Unsafe();
event.volume_id_ = volume.volume_id();
event.mount_error_ = error_code;
events_.push_back(event);
}
void OnFormatStarted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::FORMAT_STARTED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnFormatCompleted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::FORMAT_COMPLETED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnPartitionStarted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::PARTITION_STARTED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnPartitionCompleted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::PARTITION_COMPLETED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnRenameStarted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::RENAME_STARTED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnRenameCompleted(const std::string& device_path,
const std::string& device_label,
bool success) override {
Event event;
event.type_ = Event::RENAME_COMPLETED;
event.device_path_ = device_path;
event.device_label_ = device_label;
event.success_ = success;
events_.push_back(event);
}
void OnShutdownStart(VolumeManager* volume_manager) override {
// Each test should remove its observer manually, so that they're all gone
// by the time VolumeManager shuts down, and this handler is never reached.
// In fact, it's more likely for UAF crash to happen before this code is
// reached.
NOTREACHED();
}
private:
std::vector<Event> events_;
};
class ScopedLoggingObserver {
public:
explicit ScopedLoggingObserver(VolumeManager* volume_manager)
: volume_manager_(volume_manager) {
volume_manager_->AddObserver(&logging_observer_);
}
~ScopedLoggingObserver() {
volume_manager_->RemoveObserver(&logging_observer_);
}
const std::vector<LoggingObserver::Event>& events() const {
return logging_observer_.events();
}
private:
const raw_ptr<VolumeManager> volume_manager_;
LoggingObserver logging_observer_;
};
} // namespace
std::unique_ptr<KeyedService> CreateFileSystemOperationRunnerForTesting(
content::BrowserContext* context) {
return arc::ArcFileSystemOperationRunner::CreateForTesting(
context, arc::ArcServiceManager::Get()->arc_bridge_service());
}
class VolumeManagerTest : public testing::Test {
protected:
// Helper class that contains per-profile objects.
class ProfileEnvironment {
public:
ProfileEnvironment(TestingProfile* profile, DiskMountManager* disk_manager)
: profile_(profile),
extension_registry_(
std::make_unique<extensions::ExtensionRegistry>(profile_)),
file_system_provider_service_(
std::make_unique<ash::file_system_provider::Service>(
profile_,
extension_registry_.get())),
drive_integration_service_(
std::make_unique<drive::DriveIntegrationService>(
TestingBrowserProcess::GetGlobal()->local_state(),
profile_,
std::string(),
base::FilePath())),
volume_manager_(std::make_unique<VolumeManager>(
profile_,
drive_integration_service_.get(), // DriveIntegrationService
chromeos::PowerManagerClient::Get(),
disk_manager,
file_system_provider_service_.get(),
base::BindRepeating(&ProfileEnvironment::GetFakeMtpStorageInfo,
base::Unretained(this)))) {}
~ProfileEnvironment() {
// In production, KeyedServices have Shutdown() called before destruction.
volume_manager_->Shutdown();
drive_integration_service_->Shutdown();
file_system_provider_service_->Shutdown();
extension_registry_->Shutdown();
}
TestingProfile* profile() const { return profile_; }
VolumeManager* volume_manager() const { return volume_manager_.get(); }
void SetFakeMtpStorageInfo(
device::mojom::MtpStorageInfoPtr fake_mtp_storage_info) {
fake_mtp_storage_info_ = std::move(fake_mtp_storage_info);
}
private:
void GetFakeMtpStorageInfo(
const std::string& storage_name,
device::mojom::MtpManager::GetStorageInfoCallback callback) {
if (!fake_mtp_storage_info_) {
fake_mtp_storage_info_ = device::mojom::MtpStorageInfo::New();
}
std::move(callback).Run(std::move(fake_mtp_storage_info_));
}
const raw_ptr<TestingProfile> profile_;
std::unique_ptr<extensions::ExtensionRegistry> extension_registry_;
std::unique_ptr<ash::file_system_provider::Service>
file_system_provider_service_;
std::unique_ptr<drive::DriveIntegrationService> drive_integration_service_;
std::unique_ptr<VolumeManager> volume_manager_;
device::mojom::MtpStorageInfoPtr fake_mtp_storage_info_;
};
void SetUp() override {
// Some test cases exercises the "MyFiles" directory.
scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
ash::switches::kUseMyFilesInUserDataDirForTesting);
chromeos::PowerManagerClient::InitializeFake();
disk_mount_manager_ = std::make_unique<FakeDiskMountManager>();
fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
testing_profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(testing_profile_manager_->SetUp());
primary_profile_ = std::make_unique<ProfileEnvironment>(
AddLoggedInUser(AccountId::FromUserEmail("primary@test")),
disk_mount_manager_.get());
}
void TearDown() override {
task_environment_.RunUntilIdle();
primary_profile_.reset();
testing_profile_manager_->DeleteAllTestingProfiles();
disk_mount_manager_.reset();
chromeos::PowerManagerClient::Shutdown();
// ExternalMountPoints instance for the system is global singleton,
// so some states can be leaked to another test. Revoke all of them
// explicitly.
storage::ExternalMountPoints::GetSystemInstance()->RevokeAllFileSystems();
}
virtual TestingProfile* AddLoggedInUser(const AccountId& account_id) {
fake_user_manager_->AddUser(account_id);
fake_user_manager_->LoginUser(account_id);
TestingProfile* profile = testing_profile_manager_->CreateTestingProfile(
account_id.GetUserEmail());
ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
fake_user_manager_->FindUserAndModify(account_id), profile);
return profile;
}
// Accessors to the primary profile.
TestingProfile* profile() const { return primary_profile_->profile(); }
VolumeManager* volume_manager() const {
return primary_profile_->volume_manager();
}
ProfileEnvironment* primary_profile() { return primary_profile_.get(); }
base::test::ScopedCommandLine scoped_command_line_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<FakeDiskMountManager> disk_mount_manager_;
user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
fake_user_manager_;
std::unique_ptr<ProfileEnvironment> primary_profile_;
std::unique_ptr<TestingProfileManager> testing_profile_manager_;
};
TEST(VolumeTest, CreateForRemovable) {
const std::unique_ptr<Volume> volume = Volume::CreateForRemovable(
{"/source/path", "/mount/path", MountType::kDevice,
MountError::kUnknownFilesystem},
nullptr);
ASSERT_TRUE(volume);
EXPECT_EQ(volume->source_path(), FilePath("/source/path"));
EXPECT_EQ(volume->mount_path(), FilePath("/mount/path"));
EXPECT_EQ(volume->type(), VOLUME_TYPE_REMOVABLE_DISK_PARTITION);
EXPECT_EQ(volume->mount_condition(), MountError::kUnknownFilesystem);
EXPECT_EQ(volume->volume_id(), "removable:path");
EXPECT_EQ(volume->volume_label(), "path");
EXPECT_EQ(volume->source(), SOURCE_DEVICE);
EXPECT_FALSE(volume->is_read_only());
EXPECT_TRUE(volume->watchable());
}
TEST_F(VolumeManagerTest, OnDriveFileSystemMountAndUnmount) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFileSystemMounted();
ASSERT_EQ(1U, observer.events().size());
LoggingObserver::Event event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ(drive::DriveIntegrationServiceFactory::GetForProfile(profile())
->GetMountPointPath()
.AsUTF8Unsafe(),
event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
volume_manager()->OnFileSystemBeingUnmounted();
ASSERT_EQ(2U, observer.events().size());
event = observer.events()[1];
EXPECT_EQ(LoggingObserver::Event::VOLUME_UNMOUNTED, event.type());
EXPECT_EQ(drive::DriveIntegrationServiceFactory::GetForProfile(profile())
->GetMountPointPath()
.AsUTF8Unsafe(),
event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
}
TEST_F(VolumeManagerTest, OnDriveFileSystemUnmountWithoutMount) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFileSystemBeingUnmounted();
// Unmount event for non-mounted volume is not reported.
ASSERT_EQ(0U, observer.events().size());
}
TEST_F(VolumeManagerTest, OnBootDeviceDiskEvent) {
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> disk =
Disk::Builder().SetDevicePath("device1").SetOnBootDevice(true).Build();
volume_manager()->OnBootDeviceDiskEvent(DiskMountManager::DISK_ADDED, *disk);
EXPECT_EQ(0U, observer.events().size());
volume_manager()->OnBootDeviceDiskEvent(DiskMountManager::DISK_REMOVED,
*disk);
EXPECT_EQ(0U, observer.events().size());
volume_manager()->OnBootDeviceDiskEvent(DiskMountManager::DISK_CHANGED,
*disk);
EXPECT_EQ(0U, observer.events().size());
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_Hidden) {
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> disk =
Disk::Builder().SetDevicePath("device1").SetIsHidden(true).Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*disk);
EXPECT_EQ(0U, observer.events().size());
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_REMOVED,
*disk);
EXPECT_EQ(0U, observer.events().size());
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_CHANGED,
*disk);
EXPECT_EQ(0U, observer.events().size());
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_Added) {
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> empty_device_path_disk = Disk::Builder().Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*empty_device_path_disk);
EXPECT_EQ(0U, observer.events().size());
std::unique_ptr<const Disk> media_disk =
Disk::Builder().SetDevicePath("device1").SetHasMedia(true).Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*media_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADDED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_TRUE(event.mounting());
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
const FakeDiskMountManager::MountRequest& mount_request =
disk_mount_manager_->mount_requests()[0];
EXPECT_EQ("device1", mount_request.source_path);
EXPECT_EQ("", mount_request.source_format);
EXPECT_EQ("", mount_request.mount_label);
EXPECT_EQ(ash::MountType::kDevice, mount_request.type);
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_AddedNonMounting) {
// Device which is already mounted.
{
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> mounted_media_disk =
Disk::Builder()
.SetDevicePath("device1")
.SetMountPath("mounted")
.SetHasMedia(true)
.Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*mounted_media_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADDED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_FALSE(event.mounting());
ASSERT_EQ(0U, disk_mount_manager_->mount_requests().size());
}
// Device without media.
{
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> no_media_disk =
Disk::Builder().SetDevicePath("device1").Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*no_media_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADDED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_FALSE(event.mounting());
ASSERT_EQ(0U, disk_mount_manager_->mount_requests().size());
}
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_ExternalStoragePolicy) {
std::unique_ptr<const Disk> media_disk = CreateAllowlistedDisk("device1");
// Disable external storage by policy.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
true);
// Disk mounting is blocked by policy.
{
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*media_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADD_BLOCKED_BY_POLICY, event.type());
EXPECT_EQ("device1", event.device_path());
ASSERT_EQ(0U, disk_mount_manager_->mount_requests().size());
}
// Set the external storage allowlist.
SetExternalStorageAllowlist(profile()->GetPrefs());
// Disk mounting is not blocked because of the allowlist.
{
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*media_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADDED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_TRUE(event.mounting());
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
}
}
TEST_F(VolumeManagerTest, OnDiskAutoMountableEvent_Removed) {
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> mounted_disk = Disk::Builder()
.SetDevicePath("device1")
.SetMountPath("mount_path")
.Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_REMOVED,
*mounted_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_REMOVED, event.type());
EXPECT_EQ("device1", event.device_path());
ASSERT_EQ(1U, disk_mount_manager_->unmount_requests().size());
EXPECT_EQ("mount_path", disk_mount_manager_->unmount_requests()[0]);
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_RemovedNotMounted) {
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> not_mounted_disk =
Disk::Builder().SetDevicePath("device1").Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_REMOVED,
*not_mounted_disk);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_REMOVED, event.type());
EXPECT_EQ("device1", event.device_path());
ASSERT_EQ(0U, disk_mount_manager_->unmount_requests().size());
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_Changed) {
// Changed event should cause mounting (if possible).
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> disk =
Disk::Builder().SetDevicePath("device1").SetHasMedia(true).Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_CHANGED,
*disk);
EXPECT_EQ(1U, observer.events().size());
EXPECT_EQ(1U, disk_mount_manager_->mount_requests().size());
EXPECT_EQ(0U, disk_mount_manager_->unmount_requests().size());
// Read-write mode by default.
EXPECT_EQ(ash::MountAccessMode::kReadWrite,
disk_mount_manager_->mount_requests()[0].access_mode);
}
TEST_F(VolumeManagerTest, OnAutoMountableDiskEvent_ChangedInReadonly) {
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageReadOnly,
true);
// Changed event should cause mounting (if possible).
ScopedLoggingObserver observer(volume_manager());
std::unique_ptr<const Disk> disk =
Disk::Builder().SetDevicePath("device1").SetHasMedia(true).Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_CHANGED,
*disk);
EXPECT_EQ(1U, observer.events().size());
EXPECT_EQ(1U, disk_mount_manager_->mount_requests().size());
EXPECT_EQ(0U, disk_mount_manager_->unmount_requests().size());
// Should mount a disk in read-only mode.
EXPECT_EQ(ash::MountAccessMode::kReadOnly,
disk_mount_manager_->mount_requests()[0].access_mode);
}
TEST_F(VolumeManagerTest, OnDeviceEvent_Added) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnDeviceEvent(DiskMountManager::DEVICE_ADDED, "device1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DEVICE_ADDED, event.type());
EXPECT_EQ("device1", event.device_path());
}
TEST_F(VolumeManagerTest, OnDeviceEvent_Removed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnDeviceEvent(DiskMountManager::DEVICE_REMOVED, "device1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DEVICE_REMOVED, event.type());
EXPECT_EQ("device1", event.device_path());
}
TEST_F(VolumeManagerTest, OnDeviceEvent_Scanned) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnDeviceEvent(DiskMountManager::DEVICE_SCANNED, "device1");
// SCANNED event is just ignored.
EXPECT_EQ(0U, observer.events().size());
}
TEST_F(VolumeManagerTest, OnMountEvent_MountingAndUnmounting) {
ScopedLoggingObserver observer(volume_manager());
const DiskMountManager::MountPoint kMountPoint{"device1", "mount1",
ash::MountType::kDevice};
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess, kMountPoint);
ASSERT_EQ(1U, observer.events().size());
LoggingObserver::Event event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
volume_manager()->OnMountEvent(DiskMountManager::UNMOUNTING,
ash::MountError::kSuccess, kMountPoint);
ASSERT_EQ(2U, observer.events().size());
event = observer.events()[1];
EXPECT_EQ(LoggingObserver::Event::VOLUME_UNMOUNTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
}
TEST_F(VolumeManagerTest, OnMountEvent_ExternalStoragePolicy) {
disk_mount_manager_->AddDiskForTest(CreateAllowlistedDisk("device1"));
const DiskMountManager::MountPoint kMountPoint{"device1", "mount1",
ash::MountType::kDevice};
// Disable external storage by policy.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
true);
// Disk mounting is blocked by policy.
{
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess, kMountPoint);
ASSERT_EQ(1U, observer.events().size());
LoggingObserver::Event event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADD_BLOCKED_BY_POLICY, event.type());
EXPECT_EQ("device1", event.device_path());
}
// Set the external storage allowlist.
SetExternalStorageAllowlist(profile()->GetPrefs());
// Disk mounting is not blocked because of the allowlist.
{
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess, kMountPoint);
ASSERT_EQ(1U, observer.events().size());
LoggingObserver::Event event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
}
}
TEST_F(VolumeManagerTest, OnMountEvent_Remounting) {
std::unique_ptr<Disk> disk = Disk::Builder()
.SetDevicePath("device1")
.SetFileSystemUUID("uuid1")
.Build();
disk_mount_manager_->AddDiskForTest(std::move(disk));
disk_mount_manager_->MountPath("device1", "", "", {}, ash::MountType::kDevice,
ash::MountAccessMode::kReadWrite,
base::DoNothing());
const DiskMountManager::MountPoint kMountPoint{"device1", "mount1",
ash::MountType::kDevice};
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess, kMountPoint);
// Emulate system suspend and then resume.
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
// After resume, the device is unmounted and then mounted.
volume_manager()->OnMountEvent(DiskMountManager::UNMOUNTING,
ash::MountError::kSuccess, kMountPoint);
// Observe what happened for the mount event.
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess, kMountPoint);
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
}
TEST_F(VolumeManagerTest, OnMountEvent_UnmountingWithoutMounting) {
ScopedLoggingObserver observer(volume_manager());
const DiskMountManager::MountPoint kMountPoint{"device1", "mount1",
ash::MountType::kDevice};
volume_manager()->OnMountEvent(DiskMountManager::UNMOUNTING,
ash::MountError::kSuccess, kMountPoint);
// Unmount event for a disk not mounted in this manager is not reported.
ASSERT_EQ(0U, observer.events().size());
}
TEST_F(VolumeManagerTest, OnFormatEvent_Started) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFormatEvent(DiskMountManager::FORMAT_STARTED,
ash::FormatError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::FORMAT_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
}
TEST_F(VolumeManagerTest, OnFormatEvent_StartFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFormatEvent(DiskMountManager::FORMAT_STARTED,
ash::FormatError::kUnknownError, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::FORMAT_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
}
TEST_F(VolumeManagerTest, OnFormatEvent_Completed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
ash::FormatError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::FORMAT_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
// When "format" is done, VolumeManager requests to mount it.
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
const FakeDiskMountManager::MountRequest& mount_request =
disk_mount_manager_->mount_requests()[0];
EXPECT_EQ("device1", mount_request.source_path);
EXPECT_EQ("", mount_request.source_format);
EXPECT_EQ("", mount_request.mount_label);
EXPECT_EQ(ash::MountType::kDevice, mount_request.type);
}
TEST_F(VolumeManagerTest, OnFormatEvent_CompletedFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnFormatEvent(DiskMountManager::FORMAT_COMPLETED,
ash::FormatError::kUnknownError, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::FORMAT_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
// When "format" is done, VolumeManager requests to mount it.
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
const FakeDiskMountManager::MountRequest& mount_request =
disk_mount_manager_->mount_requests()[0];
EXPECT_EQ("device1", mount_request.source_path);
EXPECT_EQ("", mount_request.source_format);
EXPECT_EQ("", mount_request.mount_label);
EXPECT_EQ(ash::MountType::kDevice, mount_request.type);
}
TEST_F(VolumeManagerTest, OnPartitionEvent_Started) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnPartitionEvent(DiskMountManager::PARTITION_STARTED,
ash::PartitionError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::PARTITION_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
}
TEST_F(VolumeManagerTest, OnPartitionEvent_StartFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnPartitionEvent(DiskMountManager::PARTITION_STARTED,
ash::PartitionError::kUnknownError,
"device1", "label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::PARTITION_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
}
TEST_F(VolumeManagerTest, OnPartitionEvent_Completed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnPartitionEvent(DiskMountManager::PARTITION_COMPLETED,
ash::PartitionError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::PARTITION_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
}
TEST_F(VolumeManagerTest, OnPartitionEvent_CompletedFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnPartitionEvent(DiskMountManager::PARTITION_COMPLETED,
ash::PartitionError::kUnknownError,
"device1", "label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::PARTITION_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
// When "partitioning" fails, VolumeManager requests to mount it for retry.
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
const FakeDiskMountManager::MountRequest& mount_request =
disk_mount_manager_->mount_requests()[0];
EXPECT_EQ("device1", mount_request.source_path);
EXPECT_EQ("", mount_request.source_format);
EXPECT_EQ("", mount_request.mount_label);
EXPECT_EQ(ash::MountType::kDevice, mount_request.type);
}
TEST_F(VolumeManagerTest, OnExternalStorageDisabledChanged) {
// Set up ExternalStorageAllowlist.
disk_mount_manager_->AddDiskForTest(CreateAllowlistedDisk("mount1"));
SetExternalStorageAllowlist(profile()->GetPrefs());
// Subscribe to pref changes.
volume_manager()->Initialize();
// Create four mount points (first one is allowlisted).
disk_mount_manager_->MountPath("mount1", "", "", {}, ash::MountType::kDevice,
ash::MountAccessMode::kReadWrite,
base::DoNothing());
disk_mount_manager_->MountPath("mount2", "", "", {}, ash::MountType::kDevice,
ash::MountAccessMode::kReadOnly,
base::DoNothing());
disk_mount_manager_->MountPath(
"mount3", "", "", {}, ash::MountType::kNetworkStorage,
ash::MountAccessMode::kReadOnly, base::DoNothing());
disk_mount_manager_->MountPath(
"failed_unmount", "", "", {}, ash::MountType::kDevice,
ash::MountAccessMode::kReadWrite, base::DoNothing());
disk_mount_manager_->FailUnmountRequest("failed_unmount",
ash::MountError::kUnknownError);
// Initially, there are four mount points.
ASSERT_EQ(4U, disk_mount_manager_->mount_points().size());
ASSERT_EQ(0U, disk_mount_manager_->unmount_requests().size());
// Set kExternalStorageDisabled to false and expect no effects.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
false);
EXPECT_EQ(4U, disk_mount_manager_->mount_points().size());
EXPECT_EQ(0U, disk_mount_manager_->unmount_requests().size());
// Set kExternalStorageDisabled to true.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
true);
// Wait until all unmount request finishes, so that callback chain to unmount
// all the mount points will be invoked.
disk_mount_manager_->FinishAllUnmountPathRequests();
// External media mount points which are not allowlisted should be unmounted.
// Other mount point types should remain. The failing unmount should also
// remain.
EXPECT_EQ(3U, disk_mount_manager_->mount_points().size());
EXPECT_THAT(disk_mount_manager_->unmount_requests(),
UnorderedElementsAre("mount2", "failed_unmount"));
}
TEST_F(VolumeManagerTest, ExternalStorageDisabledPolicyMultiProfile) {
auto secondary = std::make_unique<ProfileEnvironment>(
AddLoggedInUser(AccountId::FromUserEmail("secondary@test")),
disk_mount_manager_.get());
volume_manager()->Initialize();
secondary->volume_manager()->Initialize();
// Simulates the case that the main profile has kExternalStorageDisabled set
// as false, and the secondary profile has the config set to true.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
false);
secondary->profile()->GetPrefs()->SetBoolean(
disks::prefs::kExternalStorageDisabled, true);
ScopedLoggingObserver main_observer(volume_manager());
ScopedLoggingObserver secondary_observer(secondary->volume_manager());
// Add 1 disk.
std::unique_ptr<const Disk> media_disk =
Disk::Builder().SetDevicePath("device1").SetHasMedia(true).Build();
volume_manager()->OnAutoMountableDiskEvent(DiskMountManager::DISK_ADDED,
*media_disk);
secondary->volume_manager()->OnAutoMountableDiskEvent(
DiskMountManager::DISK_ADDED, *media_disk);
// The profile with external storage enabled should have mounted the volume.
auto is_volume_mounted = [](const auto& event) {
return event.type() == LoggingObserver::Event::VOLUME_MOUNTED;
};
EXPECT_TRUE(std::ranges::any_of(main_observer.events(), is_volume_mounted));
// The other profiles with external storage disabled should have not.
EXPECT_FALSE(
std::ranges::any_of(secondary_observer.events(), is_volume_mounted));
}
TEST_F(VolumeManagerTest, OnExternalStorageReadOnlyChanged) {
// This subscribes to pref changes.
volume_manager()->Initialize();
// Set up some disks (first one is allowlisted).
disk_mount_manager_->AddDiskForTest(CreateAllowlistedDisk("device1"));
disk_mount_manager_->AddDiskForTest(
Disk::Builder().SetDevicePath("device2").Build());
// Trigger pref updates.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageReadOnly,
true);
SetExternalStorageAllowlist(profile()->GetPrefs());
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageReadOnly,
false);
// Verify that removable disk remounts are triggered.
using ash::MountAccessMode;
std::vector<FakeDiskMountManager::RemountRequest> expected = {
// ExternalStorageReadOnly set to true.
{"device1", MountAccessMode::kReadOnly},
{"device2", MountAccessMode::kReadOnly},
// ExternalStorageAllowlist set to device1.
{"device1", MountAccessMode::kReadWrite},
{"device2", MountAccessMode::kReadOnly},
// ExternalStorageReadOnly set to false.
{"device1", MountAccessMode::kReadWrite},
{"device2", MountAccessMode::kReadWrite},
};
EXPECT_EQ(expected, disk_mount_manager_->remount_requests());
}
TEST_F(VolumeManagerTest, GetVolumeList) {
volume_manager()->Initialize(); // Adds "Downloads"
std::vector<base::WeakPtr<Volume>> volume_list =
volume_manager()->GetVolumeList();
ASSERT_GT(volume_list.size(), 0u);
}
TEST_F(VolumeManagerTest, VolumeManagerInitializeMyFilesVolume) {
// Emulate running inside ChromeOS.
base::test::ScopedRunningOnChromeOS running_on_chromeos;
volume_manager()->Initialize(); // Adds "Downloads"
std::vector<base::WeakPtr<Volume>> volume_list =
volume_manager()->GetVolumeList();
ASSERT_GT(volume_list.size(), 0u);
auto volume =
std::ranges::find(volume_list, "downloads:MyFiles", &Volume::volume_id);
EXPECT_FALSE(volume == volume_list.end());
EXPECT_EQ(VOLUME_TYPE_DOWNLOADS_DIRECTORY, (*volume)->type());
}
TEST_F(VolumeManagerTest, FindVolumeById) {
volume_manager()->Initialize(); // Adds "Downloads"
base::WeakPtr<Volume> bad_volume =
volume_manager()->FindVolumeById("nonexistent");
ASSERT_FALSE(bad_volume.get());
base::WeakPtr<Volume> good_volume =
volume_manager()->FindVolumeById("downloads:MyFiles");
ASSERT_TRUE(good_volume.get());
EXPECT_EQ("downloads:MyFiles", good_volume->volume_id());
EXPECT_EQ(VOLUME_TYPE_DOWNLOADS_DIRECTORY, good_volume->type());
}
TEST_F(VolumeManagerTest, VolumeManagerInitializeShareCacheVolume) {
volume_manager()->Initialize();
base::WeakPtr<Volume> share_cache_volume =
volume_manager()->FindVolumeById("system_internal:ShareCache");
ASSERT_TRUE(share_cache_volume.get());
EXPECT_EQ("system_internal:ShareCache", share_cache_volume->volume_id());
EXPECT_EQ(VOLUME_TYPE_SYSTEM_INTERNAL, share_cache_volume->type());
}
TEST_F(VolumeManagerTest, FindVolumeFromPath) {
volume_manager()->Initialize(); // Adds "Downloads"
base::WeakPtr<Volume> downloads_volume = volume_manager()->GetVolumeList()[0];
EXPECT_EQ("downloads:MyFiles", downloads_volume->volume_id());
base::FilePath downloads_mount_path = downloads_volume->mount_path();
// FindVolumeFromPath(downloads_mount_path.DirName()) should return null
// because the path is the parent folder of the Downloads mount path.
base::WeakPtr<Volume> volume_from_path =
volume_manager()->FindVolumeFromPath(downloads_mount_path.DirName());
ASSERT_FALSE(volume_from_path);
// FindVolumeFromPath("MyFiles") should return null because it's only the last
// component of the Downloads mount path.
volume_from_path =
volume_manager()->FindVolumeFromPath(downloads_mount_path.BaseName());
ASSERT_FALSE(volume_from_path);
// FindVolumeFromPath(<Downloads mount path>) should point to the Downloads
// volume.
volume_from_path = volume_manager()->FindVolumeFromPath(downloads_mount_path);
ASSERT_TRUE(volume_from_path);
EXPECT_EQ("downloads:MyFiles", volume_from_path->volume_id());
// FindVolumeFromPath(<Downloads mount path>/folder) is on the Downloads
// volume, it should also point to the Downloads volume, even if the folder
// doesn't exist.
volume_from_path = volume_manager()->FindVolumeFromPath(
downloads_mount_path.Append("folder"));
ASSERT_TRUE(volume_from_path);
EXPECT_EQ("downloads:MyFiles", volume_from_path->volume_id());
}
TEST_F(VolumeManagerTest, ArchiveSourceFiltering) {
ScopedLoggingObserver observer(volume_manager());
// Mount a USB stick.
volume_manager()->OnMountEvent(
DiskMountManager::MOUNTING, ash::MountError::kSuccess,
{"/removable/usb", "/removable/usb", ash::MountType::kDevice});
// Mount a zip archive in the stick.
volume_manager()->OnMountEvent(
DiskMountManager::MOUNTING, ash::MountError::kSuccess,
{"/removable/usb/1.zip", "/archive/1", ash::MountType::kArchive});
base::WeakPtr<Volume> volume = volume_manager()->FindVolumeById("archive:1");
ASSERT_TRUE(volume.get());
EXPECT_EQ("/archive/1", volume->mount_path().AsUTF8Unsafe());
EXPECT_EQ(2u, observer.events().size());
// Mount a zip archive in the previous zip archive.
volume_manager()->OnMountEvent(
DiskMountManager::MOUNTING, ash::MountError::kSuccess,
{"/archive/1/2.zip", "/archive/2", ash::MountType::kArchive});
base::WeakPtr<Volume> second_volume =
volume_manager()->FindVolumeById("archive:2");
ASSERT_TRUE(second_volume.get());
EXPECT_EQ("/archive/2", second_volume->mount_path().AsUTF8Unsafe());
EXPECT_EQ(3u, observer.events().size());
// A zip file is mounted from other profile. It must be ignored in the current
// VolumeManager.
volume_manager()->OnMountEvent(DiskMountManager::MOUNTING,
ash::MountError::kSuccess,
{"/other/profile/drive/folder/3.zip",
"/archive/3", ash::MountType::kArchive});
base::WeakPtr<Volume> third_volume =
volume_manager()->FindVolumeById("archive:3");
ASSERT_FALSE(third_volume.get());
EXPECT_EQ(3u, observer.events().size());
}
TEST_F(VolumeManagerTest, MTPPlugAndUnplug) {
ScopedLoggingObserver observer(volume_manager());
storage_monitor::StorageInfo info(
storage_monitor::StorageInfo::MakeDeviceId(
storage_monitor::StorageInfo::MTP_OR_PTP, "dummy-device-id"),
FILE_PATH_LITERAL("/dummy/device/location"), u"label", u"vendor",
u"model", 12345 /* size */);
storage_monitor::StorageInfo non_mtp_info(
storage_monitor::StorageInfo::MakeDeviceId(
storage_monitor::StorageInfo::FIXED_MASS_STORAGE, "dummy-device-id2"),
FILE_PATH_LITERAL("/dummy/device/location2"), u"label2", u"vendor2",
u"model2", 12345 /* size */);
// Attach: expect mount events for the MTP and fusebox MTP volumes.
volume_manager()->OnRemovableStorageAttached(info);
ASSERT_EQ(2u, observer.events().size());
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED,
observer.events()[0].type());
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED,
observer.events()[1].type());
// The MTP volume should be mounted.
base::WeakPtr<Volume> volume = volume_manager()->FindVolumeById("mtp:model");
ASSERT_TRUE(volume);
EXPECT_EQ("", volume->file_system_type());
EXPECT_EQ(VOLUME_TYPE_MTP, volume->type());
// The fusebox MTP volume should be mounted.
const auto fusebox_volume_id = base::StrCat({util::kFuseBox, "mtp:model"});
base::WeakPtr<Volume> fusebox_volume =
volume_manager()->FindVolumeById(fusebox_volume_id);
ASSERT_TRUE(fusebox_volume);
EXPECT_EQ(util::kFuseBox, fusebox_volume->file_system_type());
EXPECT_EQ(VOLUME_TYPE_MTP, fusebox_volume->type());
// Non MTP attach events from storage monitor are ignored.
volume_manager()->OnRemovableStorageAttached(non_mtp_info);
EXPECT_EQ(2u, observer.events().size());
// Detach: there should be two more events, bringing the total to four.
volume_manager()->OnRemovableStorageDetached(info);
ASSERT_EQ(4u, observer.events().size());
EXPECT_EQ(LoggingObserver::Event::VOLUME_UNMOUNTED,
observer.events()[2].type());
EXPECT_EQ(LoggingObserver::Event::VOLUME_UNMOUNTED,
observer.events()[3].type());
// The unmount events should remove the MTP and fusebox MTP volumes.
EXPECT_FALSE(volume);
EXPECT_FALSE(fusebox_volume);
}
TEST_F(VolumeManagerTest, MTP_ExternalStoragePolicy) {
storage_monitor::StorageInfo info(
storage_monitor::StorageInfo::MakeDeviceId(
storage_monitor::StorageInfo::MTP_OR_PTP, "dummy-device-id"),
FILE_PATH_LITERAL("/dummy/device/location"), u"label", u"vendor",
u"model", 12345 /* size */);
// Disable external storage by policy.
profile()->GetPrefs()->SetBoolean(disks::prefs::kExternalStorageDisabled,
true);
// Attach is blocked by policy.
{
ScopedLoggingObserver observer(volume_manager());
primary_profile()->SetFakeMtpStorageInfo(
CreateAllowlistedMtpStorageInfo("dummy/device/location"));
volume_manager()->OnRemovableStorageAttached(info);
ASSERT_EQ(1u, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::DISK_ADD_BLOCKED_BY_POLICY, event.type());
EXPECT_EQ("/dummy/device/location", event.device_path());
}
// Set the external storage allowlist.
SetExternalStorageAllowlist(profile()->GetPrefs());
// Attach is not blocked because of the allowlist.
{
ScopedLoggingObserver observer(volume_manager());
primary_profile()->SetFakeMtpStorageInfo(
CreateAllowlistedMtpStorageInfo("dummy/device/location"));
volume_manager()->OnRemovableStorageAttached(info);
ASSERT_EQ(2u, observer.events().size());
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED,
observer.events()[0].type());
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED,
observer.events()[1].type());
}
// Cleanup. Detach storage, otherwise crashes in ~MTPDeviceMapService.
volume_manager()->OnRemovableStorageDetached(info);
}
TEST_F(VolumeManagerTest, OnRenameEvent_Started) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnRenameEvent(DiskMountManager::RENAME_STARTED,
ash::RenameError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::RENAME_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
}
TEST_F(VolumeManagerTest, OnRenameEvent_StartFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnRenameEvent(DiskMountManager::RENAME_STARTED,
ash::RenameError::kUnknownError, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::RENAME_STARTED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
}
TEST_F(VolumeManagerTest, OnRenameEvent_Completed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnRenameEvent(DiskMountManager::RENAME_COMPLETED,
ash::RenameError::kSuccess, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::RENAME_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_TRUE(event.success());
// When "rename" is successfully done, VolumeManager requests to mount it.
ASSERT_EQ(1U, disk_mount_manager_->mount_requests().size());
const FakeDiskMountManager::MountRequest& mount_request =
disk_mount_manager_->mount_requests()[0];
EXPECT_EQ("device1", mount_request.source_path);
EXPECT_EQ("", mount_request.source_format);
EXPECT_EQ(ash::MountType::kDevice, mount_request.type);
}
TEST_F(VolumeManagerTest, OnRenameEvent_CompletedFailed) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnRenameEvent(DiskMountManager::RENAME_COMPLETED,
ash::RenameError::kUnknownError, "device1",
"label1");
ASSERT_EQ(1U, observer.events().size());
const LoggingObserver::Event& event = observer.events()[0];
EXPECT_EQ(LoggingObserver::Event::RENAME_COMPLETED, event.type());
EXPECT_EQ("device1", event.device_path());
EXPECT_EQ("label1", event.device_label());
EXPECT_FALSE(event.success());
EXPECT_EQ(1U, disk_mount_manager_->mount_requests().size());
}
TEST_F(VolumeManagerTest, VolumeManagerInitializeForMultiProfiles) {
auto secondary_profile = std::make_unique<ProfileEnvironment>(
AddLoggedInUser(AccountId::FromUserEmail("secondary@test")),
disk_mount_manager_.get());
volume_manager()->Initialize();
secondary_profile->volume_manager()->Initialize();
// Different profiles' shared cache and download volumes
// should have different `mount_name`, see crbug.com/365173555.
std::vector<storage::MountPoints::MountPointInfo> mount_point_infos;
storage::ExternalMountPoints::GetSystemInstance()->AddMountPointInfosTo(
&mount_point_infos);
std::unordered_set<std::string> mount_point_names;
for (const auto& mount_point_info : mount_point_infos) {
mount_point_names.insert(mount_point_info.name);
}
ASSERT_THAT(mount_point_names, testing::SizeIs(4));
EXPECT_THAT(
mount_point_names,
testing::UnorderedElementsAre(
util::GetDownloadsMountPointName(profile()),
util::GetDownloadsMountPointName(secondary_profile->profile()),
util::GetShareCacheMountPointName(profile()),
util::GetShareCacheMountPointName(secondary_profile->profile())));
}
// Test fixture for VolumeManager tests with ARC enabled.
class VolumeManagerArcTest : public VolumeManagerTest {
protected:
void SetUp() override {
scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
ash::switches::kArcAvailability, "officially-supported");
VolumeManagerTest::SetUp();
}
void TearDown() override {
arc_service_manager_->arc_bridge_service()->file_system()->CloseInstance(
&file_system_instance_);
arc_service_manager_->set_browser_context(nullptr);
VolumeManagerTest::TearDown();
}
TestingProfile* AddLoggedInUser(const AccountId& account_id) override {
TestingProfile* profile = VolumeManagerTest::AddLoggedInUser(account_id);
// Set up an Arc service manager with a fake file system. This must be done
// before initializing VolumeManager() to make its dependency
// DocumentsProviderRootManager work.
CHECK(!arc_service_manager_);
arc_service_manager_ = std::make_unique<arc::ArcServiceManager>();
arc_service_manager_->set_browser_context(profile);
arc::ArcFileSystemOperationRunner::GetFactory()->SetTestingFactoryAndUse(
profile,
base::BindRepeating(&CreateFileSystemOperationRunnerForTesting));
arc_service_manager_->arc_bridge_service()->file_system()->SetInstance(
&file_system_instance_);
arc::WaitForInstanceReady(
arc_service_manager_->arc_bridge_service()->file_system());
EXPECT_TRUE(file_system_instance_.InitCalled());
return profile;
}
private:
arc::FakeFileSystemInstance file_system_instance_;
std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
};
TEST_F(VolumeManagerArcTest, OnArcPlayStoreEnabledChanged_Enabled) {
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnArcPlayStoreEnabledChanged(true);
ASSERT_EQ(5U, observer.events().size());
size_t index = 0;
for (const auto& event : observer.events()) {
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
if (index < 4) {
EXPECT_EQ(arc::GetMediaViewVolumeId(arc_volume_ids[index]),
event.volume_id());
} else {
EXPECT_EQ(arc_volume_ids[index], event.volume_id());
}
index++;
}
}
TEST_F(VolumeManagerArcTest, OnArcPlayStoreEnabledChanged_Disabled) {
// Need to enable it first before disabling it, otherwise
// it will be no-op.
volume_manager()->OnArcPlayStoreEnabledChanged(true);
ScopedLoggingObserver observer(volume_manager());
volume_manager()->OnArcPlayStoreEnabledChanged(false);
ASSERT_EQ(5U, observer.events().size());
size_t index = 0;
for (const auto& event : observer.events()) {
EXPECT_EQ(LoggingObserver::Event::VOLUME_UNMOUNTED, event.type());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
if (index < 4) {
EXPECT_EQ(arc::GetMediaViewVolumeId(arc_volume_ids[index]),
event.volume_id());
} else {
EXPECT_EQ(arc_volume_ids[index], event.volume_id());
}
index++;
}
}
TEST_F(VolumeManagerArcTest, ShouldAlwaysMountAndroidVolumesInFilesForTesting) {
base::test::ScopedCommandLine command_line;
command_line.GetProcessCommandLine()->AppendSwitch(
ash::switches::kArcForceMountAndroidVolumesInFiles);
ScopedLoggingObserver observer(volume_manager());
// Volumes are mounted even when Play Store is not enabled for the profile.
volume_manager()->OnArcPlayStoreEnabledChanged(false);
ASSERT_EQ(5U, observer.events().size());
size_t index = 0;
for (const auto& event : observer.events()) {
EXPECT_EQ(LoggingObserver::Event::VOLUME_MOUNTED, event.type());
EXPECT_EQ(ash::MountError::kSuccess, event.mount_error());
if (index < 4) {
EXPECT_EQ(arc::GetMediaViewVolumeId(arc_volume_ids[index]),
event.volume_id());
} else {
EXPECT_EQ(arc_volume_ids[index], event.volume_id());
}
index++;
}
// No volume-related event happens after Play Store preference changes,
// because volumes are just kept being mounted.
volume_manager()->OnArcPlayStoreEnabledChanged(true);
volume_manager()->OnArcPlayStoreEnabledChanged(false);
ASSERT_EQ(5U, observer.events().size());
}
// Tests VolumeManager with the LocalUserFilesAllowed policy.
class VolumeManagerLocalUserFilesTest : public VolumeManagerArcTest {
public:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{features::kSkyVault, features::kSkyVaultV2}, {});
VolumeManagerArcTest::SetUp();
}
void TearDown() override { VolumeManagerArcTest::TearDown(); }
void SetLocalUserFilesPolicy(bool allowed) {
TestingBrowserProcess::GetGlobal()->local_state()->SetBoolean(
prefs::kLocalUserFilesAllowed, allowed);
}
void SetLocalUserFilesMigrationPolicy(const std::string& destination) {
TestingBrowserProcess::GetGlobal()->local_state()->SetString(
prefs::kLocalUserFilesMigrationDestination, destination);
volume_manager()->OnMigrationSucceededForTesting();
}
bool ContainsDownloads() {
std::vector<base::WeakPtr<Volume>> volume_list =
volume_manager()->GetVolumeList();
if (volume_list.size() == 0u) {
return false;
}
auto volume =
std::ranges::find(volume_list, "downloads:MyFiles", &Volume::volume_id);
return volume != volume_list.end() &&
(*volume)->type() == VOLUME_TYPE_DOWNLOADS_DIRECTORY;
}
bool ContainsPlayFiles() {
std::vector<base::WeakPtr<Volume>> volume_list =
volume_manager()->GetVolumeList();
if (volume_list.size() == 0u) {
return false;
}
auto volume =
std::ranges::find(volume_list, "android_files:0", &Volume::volume_id);
return volume != volume_list.end() &&
(*volume)->type() == VOLUME_TYPE_ANDROID_FILES;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that VolumeManager removes local volumes when the policy is set to
// false, and adds them when set to true.
TEST_F(VolumeManagerLocalUserFilesTest, DisableEnable) {
// Enable ARC.
profile()->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true);
// Emulate running inside ChromeOS.
base::test::ScopedRunningOnChromeOS running_on_chromeos;
volume_manager()->Initialize(); // Adds "Downloads" and "Play Files"
EXPECT_TRUE(ContainsDownloads());
EXPECT_TRUE(ContainsPlayFiles());
// Setting the policy to false removes only "Play Files".
SetLocalUserFilesPolicy(/*allowed=*/false);
EXPECT_TRUE(ContainsDownloads());
EXPECT_FALSE(ContainsPlayFiles());
// Setting the migration policy removes also "Downloads".
SetLocalUserFilesMigrationPolicy(download_dir_util::kLocationGoogleDrive);
EXPECT_FALSE(ContainsDownloads());
EXPECT_FALSE(ContainsPlayFiles());
// Setting the policy to true adds local volumes.
SetLocalUserFilesPolicy(/*allowed=*/true);
EXPECT_TRUE(ContainsDownloads());
EXPECT_TRUE(ContainsPlayFiles());
// Another update with the same value shouldn't do anything.
SetLocalUserFilesPolicy(/*allowed=*/true);
EXPECT_TRUE(ContainsDownloads());
EXPECT_TRUE(ContainsPlayFiles());
}
} // namespace file_manager