blob: 65b8636621390e87c42476ab8b4aec324c01c990 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/storage_monitor/storage_monitor_chromeos.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/disks/disk.h"
#include "chromeos/disks/mock_disk_mount_manager.h"
#include "components/storage_monitor/mock_removable_storage_observer.h"
#include "components/storage_monitor/removable_device_constants.h"
#include "components/storage_monitor/storage_info.h"
#include "components/storage_monitor/test_media_transfer_protocol_manager_chromeos.h"
#include "components/storage_monitor/test_storage_monitor.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace storage_monitor {
namespace {
using chromeos::disks::Disk;
using chromeos::disks::DiskMountManager;
using testing::_;
const char kDevice1[] = "/dev/d1";
const char kDevice1Name[] = "d1";
const char kDevice2[] = "/dev/disk/d2";
const char kDevice2Name[] = "d2";
const char kEmptyDeviceLabel[] = "";
const char kMountPointA[] = "mnt_a";
const char kMountPointB[] = "mnt_b";
const char kSDCardDeviceName1[] = "8.6 MB Amy_SD";
const char kSDCardDeviceName2[] = "8.6 MB SD Card";
const char kSDCardMountPoint1[] = "media/removable/Amy_SD";
const char kSDCardMountPoint2[] = "media/removable/SD Card";
const char kProductName[] = "Z101";
const char kUniqueId1[] = "FFFF-FFFF";
const char kUniqueId2[] = "FFFF-FF0F";
const char kVendorName[] = "CompanyA";
const char kFileSystemType[] = "exfat";
uint64_t kDevice1SizeInBytes = 113048;
uint64_t kDevice2SizeInBytes = 212312;
uint64_t kSDCardSizeInBytes = 9000000;
std::string GetDCIMDeviceId(const std::string& unique_id) {
return StorageInfo::MakeDeviceId(
StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
kFSUniqueIdPrefix + unique_id);
}
// A test version of StorageMonitorCros that exposes protected methods to tests.
class TestStorageMonitorCros : public StorageMonitorCros {
public:
TestStorageMonitorCros() {}
~TestStorageMonitorCros() override {}
void Init() override {
device::mojom::MtpManagerPtr fake_mtp_manager_ptr;
auto* fake_mtp_manager =
TestMediaTransferProtocolManagerChromeOS::GetFakeMtpManager();
fake_mtp_manager->AddBinding(mojo::MakeRequest(&fake_mtp_manager_ptr));
SetMediaTransferProtocolManagerForTest(std::move(fake_mtp_manager_ptr));
StorageMonitorCros::Init();
}
void OnMountEvent(
DiskMountManager::MountEvent event,
chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info) override {
StorageMonitorCros::OnMountEvent(event, error_code, mount_info);
}
void OnBootDeviceDiskEvent(DiskMountManager::DiskEvent event,
const chromeos::disks::Disk& disk) override {
StorageMonitorCros::OnBootDeviceDiskEvent(event, disk);
}
bool GetStorageInfoForPath(const base::FilePath& path,
StorageInfo* device_info) const override {
return StorageMonitorCros::GetStorageInfoForPath(path, device_info);
}
void EjectDevice(const std::string& device_id,
base::Callback<void(EjectStatus)> callback) override {
StorageMonitorCros::EjectDevice(device_id, callback);
}
private:
DISALLOW_COPY_AND_ASSIGN(TestStorageMonitorCros);
};
// Wrapper class to test StorageMonitorCros.
class StorageMonitorCrosTest : public testing::Test {
public:
StorageMonitorCrosTest();
~StorageMonitorCrosTest() override;
void EjectNotify(StorageMonitor::EjectStatus status);
protected:
// testing::Test:
void SetUp() override;
void TearDown() override;
void MountDevice(chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info,
const std::string& unique_id,
const std::string& device_label,
const std::string& vendor_name,
const std::string& product_name,
chromeos::DeviceType device_type,
uint64_t device_size_in_bytes);
void UnmountDevice(chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info);
uint64_t GetDeviceStorageSize(const std::string& device_location);
// Create a directory named |dir| relative to the test directory.
// Set |with_dcim_dir| to true if the created directory will have a "DCIM"
// subdirectory.
// Returns the full path to the created directory on success, or an empty
// path on failure.
base::FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir);
MockRemovableStorageObserver& observer() {
return *mock_storage_observer_;
}
TestStorageMonitorCros* monitor_;
// Owned by DiskMountManager.
chromeos::disks::MockDiskMountManager* disk_mount_manager_mock_;
StorageMonitor::EjectStatus status_;
private:
content::TestBrowserThreadBundle thread_bundle_;
// Temporary directory for created test data.
base::ScopedTempDir scoped_temp_dir_;
// Objects that talks with StorageMonitorCros.
std::unique_ptr<MockRemovableStorageObserver> mock_storage_observer_;
DISALLOW_COPY_AND_ASSIGN(StorageMonitorCrosTest);
};
StorageMonitorCrosTest::StorageMonitorCrosTest()
: monitor_(NULL),
disk_mount_manager_mock_(NULL),
status_(StorageMonitor::EJECT_FAILURE) {}
StorageMonitorCrosTest::~StorageMonitorCrosTest() {
}
void StorageMonitorCrosTest::SetUp() {
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
disk_mount_manager_mock_ = new chromeos::disks::MockDiskMountManager();
DiskMountManager::InitializeForTesting(disk_mount_manager_mock_);
disk_mount_manager_mock_->SetupDefaultReplies();
mock_storage_observer_.reset(new MockRemovableStorageObserver);
// Initialize the test subject.
TestStorageMonitor::Destroy();
monitor_ = new TestStorageMonitorCros();
std::unique_ptr<StorageMonitor> pass_monitor(monitor_);
StorageMonitor::SetStorageMonitorForTesting(std::move(pass_monitor));
monitor_->Init();
monitor_->AddObserver(mock_storage_observer_.get());
}
void StorageMonitorCrosTest::TearDown() {
monitor_->RemoveObserver(mock_storage_observer_.get());
monitor_ = NULL;
disk_mount_manager_mock_ = NULL;
DiskMountManager::Shutdown();
thread_bundle_.RunUntilIdle();
}
void StorageMonitorCrosTest::MountDevice(
chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info,
const std::string& unique_id,
const std::string& device_label,
const std::string& vendor_name,
const std::string& product_name,
chromeos::DeviceType device_type,
uint64_t device_size_in_bytes) {
if (error_code == chromeos::MOUNT_ERROR_NONE) {
disk_mount_manager_mock_->CreateDiskEntryForMountDevice(
mount_info, unique_id, device_label, vendor_name, product_name,
device_type, device_size_in_bytes, false /* is_parent */,
true /* has_media */, false /* on_boot_device */,
true /* on_removable_device */, kFileSystemType);
}
monitor_->OnMountEvent(DiskMountManager::MOUNTING, error_code, mount_info);
thread_bundle_.RunUntilIdle();
}
void StorageMonitorCrosTest::UnmountDevice(
chromeos::MountError error_code,
const DiskMountManager::MountPointInfo& mount_info) {
monitor_->OnMountEvent(DiskMountManager::UNMOUNTING, error_code, mount_info);
if (error_code == chromeos::MOUNT_ERROR_NONE)
disk_mount_manager_mock_->RemoveDiskEntryForMountDevice(mount_info);
thread_bundle_.RunUntilIdle();
}
uint64_t StorageMonitorCrosTest::GetDeviceStorageSize(
const std::string& device_location) {
StorageInfo info;
if (!monitor_->GetStorageInfoForPath(base::FilePath(device_location), &info))
return 0;
return info.total_size_in_bytes();
}
base::FilePath StorageMonitorCrosTest::CreateMountPoint(
const std::string& dir, bool with_dcim_dir) {
base::FilePath return_path(scoped_temp_dir_.GetPath());
return_path = return_path.AppendASCII(dir);
base::FilePath path(return_path);
if (with_dcim_dir)
path = path.Append(kDCIMDirectoryName);
if (!base::CreateDirectory(path))
return base::FilePath();
return return_path;
}
void StorageMonitorCrosTest::EjectNotify(StorageMonitor::EjectStatus status) {
status_ = status;
}
// Simple test case where we attach and detach a media device.
TEST_F(StorageMonitorCrosTest, BasicAttachDetach) {
base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
ASSERT_FALSE(mount_path1.empty());
DiskMountManager::MountPointInfo mount_info(
kDevice1,
mount_path1.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId1,
kDevice1Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_attached().device_id());
EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_detached().device_id());
base::FilePath mount_path2 = CreateMountPoint(kMountPointB, true);
ASSERT_FALSE(mount_path2.empty());
DiskMountManager::MountPointInfo mount_info2(
kDevice2,
mount_path2.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info2,
kUniqueId2,
kDevice2Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice2SizeInBytes);
EXPECT_EQ(2, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_attached().device_id());
EXPECT_EQ(mount_path2.value(), observer().last_attached().location());
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2);
EXPECT_EQ(2, observer().attach_calls());
EXPECT_EQ(2, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_detached().device_id());
}
// Removable mass storage devices with no dcim folder are also recognized.
TEST_F(StorageMonitorCrosTest, NoDCIM) {
testing::Sequence mock_sequence;
base::FilePath mount_path = CreateMountPoint(kMountPointA, false);
const std::string kUniqueId = "FFFF-FFFF";
ASSERT_FALSE(mount_path.empty());
DiskMountManager::MountPointInfo mount_info(
kDevice1,
mount_path.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
const std::string device_id = StorageInfo::MakeDeviceId(
StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
kFSUniqueIdPrefix + kUniqueId);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId,
kDevice1Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
EXPECT_EQ(device_id, observer().last_attached().device_id());
EXPECT_EQ(mount_path.value(), observer().last_attached().location());
}
// Non device mounts and mount errors are ignored.
TEST_F(StorageMonitorCrosTest, Ignore) {
testing::Sequence mock_sequence;
base::FilePath mount_path = CreateMountPoint(kMountPointA, true);
const std::string kUniqueId = "FFFF-FFFF";
ASSERT_FALSE(mount_path.empty());
// Mount error.
DiskMountManager::MountPointInfo mount_info(
kDevice1,
mount_path.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_UNKNOWN,
mount_info,
kUniqueId,
kDevice1Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(0, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
// Not a device
mount_info.mount_type = chromeos::MOUNT_TYPE_ARCHIVE;
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId,
kDevice1Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(0, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
// Unsupported file system.
mount_info.mount_type = chromeos::MOUNT_TYPE_DEVICE;
mount_info.mount_condition =
chromeos::disks::MOUNT_CONDITION_UNSUPPORTED_FILESYSTEM;
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId,
kDevice1Name,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(0, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
}
TEST_F(StorageMonitorCrosTest, SDCardAttachDetach) {
base::FilePath mount_path1 = CreateMountPoint(kSDCardMountPoint1, true);
ASSERT_FALSE(mount_path1.empty());
DiskMountManager::MountPointInfo mount_info1(
kSDCardDeviceName1,
mount_path1.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info1,
kUniqueId2,
kSDCardDeviceName1,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_SD,
kSDCardSizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_attached().device_id());
EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info1);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_detached().device_id());
base::FilePath mount_path2 = CreateMountPoint(kSDCardMountPoint2, true);
ASSERT_FALSE(mount_path2.empty());
DiskMountManager::MountPointInfo mount_info2(
kSDCardDeviceName2,
mount_path2.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info2,
kUniqueId2,
kSDCardDeviceName2,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_SD,
kSDCardSizeInBytes);
EXPECT_EQ(2, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_attached().device_id());
EXPECT_EQ(mount_path2.value(), observer().last_attached().location());
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2);
EXPECT_EQ(2, observer().attach_calls());
EXPECT_EQ(2, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId2),
observer().last_detached().device_id());
}
TEST_F(StorageMonitorCrosTest, AttachDeviceWithEmptyLabel) {
base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
ASSERT_FALSE(mount_path1.empty());
DiskMountManager::MountPointInfo mount_info(
kEmptyDeviceLabel,
mount_path1.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId1,
kEmptyDeviceLabel,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_attached().device_id());
EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_detached().device_id());
}
TEST_F(StorageMonitorCrosTest, GetStorageSize) {
base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
ASSERT_FALSE(mount_path1.empty());
DiskMountManager::MountPointInfo mount_info(
kEmptyDeviceLabel,
mount_path1.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId1,
kEmptyDeviceLabel,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_attached().device_id());
EXPECT_EQ(mount_path1.value(), observer().last_attached().location());
EXPECT_EQ(kDevice1SizeInBytes, GetDeviceStorageSize(mount_path1.value()));
UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(1, observer().detach_calls());
EXPECT_EQ(GetDCIMDeviceId(kUniqueId1),
observer().last_detached().device_id());
}
TEST_F(StorageMonitorCrosTest, EjectTest) {
base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true);
ASSERT_FALSE(mount_path1.empty());
DiskMountManager::MountPointInfo mount_info(
kEmptyDeviceLabel,
mount_path1.value(),
chromeos::MOUNT_TYPE_DEVICE,
chromeos::disks::MOUNT_CONDITION_NONE);
MountDevice(chromeos::MOUNT_ERROR_NONE,
mount_info,
kUniqueId1,
kEmptyDeviceLabel,
kVendorName,
kProductName,
chromeos::DEVICE_TYPE_USB,
kDevice1SizeInBytes);
EXPECT_EQ(1, observer().attach_calls());
EXPECT_EQ(0, observer().detach_calls());
// testing::Invoke doesn't handle move-only types, so use a lambda instead.
ON_CALL(*disk_mount_manager_mock_, UnmountPath(_, _, _))
.WillByDefault([](const std::string& location,
chromeos::UnmountOptions options,
DiskMountManager::UnmountPathCallback cb) {
std::move(cb).Run(chromeos::MOUNT_ERROR_NONE);
});
EXPECT_CALL(*disk_mount_manager_mock_,
UnmountPath(observer().last_attached().location(), _, _));
monitor_->EjectDevice(observer().last_attached().device_id(),
base::Bind(&StorageMonitorCrosTest::EjectNotify,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(StorageMonitor::EJECT_OK, status_);
}
TEST_F(StorageMonitorCrosTest, FixedStroageTest) {
const std::string uuid = "fixed1-uuid";
const std::string mount_point = "/mnt/stateful_partition";
// Fixed storage (stateful partition) added.
const std::string label = "fixed1";
std::unique_ptr<const Disk> disk = Disk::Builder()
.SetMountPath(mount_point)
.SetDeviceLabel(label)
.SetFileSystemUUID(uuid)
.Build();
monitor_->OnBootDeviceDiskEvent(DiskMountManager::DiskEvent::DISK_ADDED,
*disk);
std::vector<StorageInfo> disks = monitor_->GetAllAvailableStorages();
ASSERT_EQ(1U, disks.size());
EXPECT_EQ(mount_point, disks[0].location());
EXPECT_EQ(base::ASCIIToUTF16(label), disks[0].storage_label());
// Fixed storage (not stateful partition) added - ignore.
std::unique_ptr<const Disk> ignored_disk =
Disk::Builder()
.SetMountPath("usr/share/OEM")
.SetDeviceLabel("fixed2")
.SetFileSystemUUID("fixed2-uuid")
.Build();
monitor_->OnBootDeviceDiskEvent(DiskMountManager::DiskEvent::DISK_ADDED,
*ignored_disk);
disks = monitor_->GetAllAvailableStorages();
ASSERT_EQ(1U, disks.size());
EXPECT_EQ(mount_point, disks[0].location());
EXPECT_EQ(base::ASCIIToUTF16(label), disks[0].storage_label());
// Fixed storage (stateful partition) removed.
monitor_->OnBootDeviceDiskEvent(DiskMountManager::DiskEvent::DISK_REMOVED,
*disk);
disks = monitor_->GetAllAvailableStorages();
EXPECT_EQ(0U, disks.size());
}
} // namespace
} // namespace storage_monitor