1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "components/storage_monitor/storage_monitor_chromeos.h" 6 7 #include "base/files/file_util.h" 8 #include "base/files/scoped_temp_dir.h" 9 #include "base/logging.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/run_loop.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chromeos/disks/mock_disk_mount_manager.h" 14 #include "components/storage_monitor/mock_removable_storage_observer.h" 15 #include "components/storage_monitor/removable_device_constants.h" 16 #include "components/storage_monitor/storage_info.h" 17 #include "components/storage_monitor/test_media_transfer_protocol_manager_linux.h" 18 #include "components/storage_monitor/test_storage_monitor.h" 19 #include "content/public/browser/browser_thread.h" 20 #include "content/public/test/test_browser_thread_bundle.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 23 namespace storage_monitor { 24 25 namespace { 26 27 using content::BrowserThread; 28 using chromeos::disks::DiskMountManager; 29 using testing::_; 30 31 const char kDevice1[] = "/dev/d1"; 32 const char kDevice1Name[] = "d1"; 33 const char kDevice2[] = "/dev/disk/d2"; 34 const char kDevice2Name[] = "d2"; 35 const char kEmptyDeviceLabel[] = ""; 36 const char kMountPointA[] = "mnt_a"; 37 const char kMountPointB[] = "mnt_b"; 38 const char kSDCardDeviceName1[] = "8.6 MB Amy_SD"; 39 const char kSDCardDeviceName2[] = "8.6 MB SD Card"; 40 const char kSDCardMountPoint1[] = "media/removable/Amy_SD"; 41 const char kSDCardMountPoint2[] = "media/removable/SD Card"; 42 const char kProductName[] = "Z101"; 43 const char kUniqueId1[] = "FFFF-FFFF"; 44 const char kUniqueId2[] = "FFFF-FF0F"; 45 const char kVendorName[] = "CompanyA"; 46 47 uint64 kDevice1SizeInBytes = 113048; 48 uint64 kDevice2SizeInBytes = 212312; 49 uint64 kSDCardSizeInBytes = 9000000; 50 51 std::string GetDCIMDeviceId(const std::string& unique_id) { 52 return StorageInfo::MakeDeviceId( 53 StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM, 54 kFSUniqueIdPrefix + unique_id); 55 } 56 57 // A test version of StorageMonitorCros that exposes protected methods to tests. 58 class TestStorageMonitorCros : public StorageMonitorCros { 59 public: 60 TestStorageMonitorCros() {} 61 62 virtual ~TestStorageMonitorCros() {} 63 64 virtual void Init() OVERRIDE { 65 SetMediaTransferProtocolManagerForTest( 66 new TestMediaTransferProtocolManagerLinux()); 67 StorageMonitorCros::Init(); 68 } 69 70 virtual void OnMountEvent(DiskMountManager::MountEvent event, 71 chromeos::MountError error_code, 72 const DiskMountManager::MountPointInfo& mount_info) OVERRIDE { 73 StorageMonitorCros::OnMountEvent(event, error_code, mount_info); 74 } 75 76 virtual bool GetStorageInfoForPath(const base::FilePath& path, 77 StorageInfo* device_info) const OVERRIDE { 78 return StorageMonitorCros::GetStorageInfoForPath(path, device_info); 79 } 80 virtual void EjectDevice( 81 const std::string& device_id, 82 base::Callback<void(EjectStatus)> callback) OVERRIDE { 83 StorageMonitorCros::EjectDevice(device_id, callback); 84 } 85 86 private: 87 DISALLOW_COPY_AND_ASSIGN(TestStorageMonitorCros); 88 }; 89 90 // Wrapper class to test StorageMonitorCros. 91 class StorageMonitorCrosTest : public testing::Test { 92 public: 93 StorageMonitorCrosTest(); 94 virtual ~StorageMonitorCrosTest(); 95 96 void EjectNotify(StorageMonitor::EjectStatus status); 97 98 protected: 99 // testing::Test: 100 virtual void SetUp() OVERRIDE; 101 virtual void TearDown() OVERRIDE; 102 103 void MountDevice(chromeos::MountError error_code, 104 const DiskMountManager::MountPointInfo& mount_info, 105 const std::string& unique_id, 106 const std::string& device_label, 107 const std::string& vendor_name, 108 const std::string& product_name, 109 chromeos::DeviceType device_type, 110 uint64 device_size_in_bytes); 111 112 void UnmountDevice(chromeos::MountError error_code, 113 const DiskMountManager::MountPointInfo& mount_info); 114 115 uint64 GetDeviceStorageSize(const std::string& device_location); 116 117 // Create a directory named |dir| relative to the test directory. 118 // Set |with_dcim_dir| to true if the created directory will have a "DCIM" 119 // subdirectory. 120 // Returns the full path to the created directory on success, or an empty 121 // path on failure. 122 base::FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir); 123 124 static void PostQuitToUIThread(); 125 static void WaitForFileThread(); 126 127 MockRemovableStorageObserver& observer() { 128 return *mock_storage_observer_; 129 } 130 131 TestStorageMonitorCros* monitor_; 132 133 // Owned by DiskMountManager. 134 chromeos::disks::MockDiskMountManager* disk_mount_manager_mock_; 135 136 StorageMonitor::EjectStatus status_; 137 138 private: 139 content::TestBrowserThreadBundle thread_bundle_; 140 141 // Temporary directory for created test data. 142 base::ScopedTempDir scoped_temp_dir_; 143 144 // Objects that talks with StorageMonitorCros. 145 scoped_ptr<MockRemovableStorageObserver> mock_storage_observer_; 146 147 DISALLOW_COPY_AND_ASSIGN(StorageMonitorCrosTest); 148 }; 149 150 StorageMonitorCrosTest::StorageMonitorCrosTest() 151 : monitor_(NULL), 152 disk_mount_manager_mock_(NULL), 153 status_(StorageMonitor::EJECT_FAILURE), 154 thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD) { 155 } 156 157 StorageMonitorCrosTest::~StorageMonitorCrosTest() { 158 } 159 160 void StorageMonitorCrosTest::SetUp() { 161 ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); 162 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); 163 disk_mount_manager_mock_ = new chromeos::disks::MockDiskMountManager(); 164 DiskMountManager::InitializeForTesting(disk_mount_manager_mock_); 165 disk_mount_manager_mock_->SetupDefaultReplies(); 166 167 mock_storage_observer_.reset(new MockRemovableStorageObserver); 168 169 // Initialize the test subject. 170 TestStorageMonitor::Destroy(); 171 monitor_ = new TestStorageMonitorCros(); 172 scoped_ptr<StorageMonitor> pass_monitor(monitor_); 173 StorageMonitor::SetStorageMonitorForTesting(pass_monitor.Pass()); 174 175 monitor_->Init(); 176 monitor_->AddObserver(mock_storage_observer_.get()); 177 } 178 179 void StorageMonitorCrosTest::TearDown() { 180 monitor_->RemoveObserver(mock_storage_observer_.get()); 181 monitor_ = NULL; 182 183 disk_mount_manager_mock_ = NULL; 184 DiskMountManager::Shutdown(); 185 WaitForFileThread(); 186 } 187 188 void StorageMonitorCrosTest::MountDevice( 189 chromeos::MountError error_code, 190 const DiskMountManager::MountPointInfo& mount_info, 191 const std::string& unique_id, 192 const std::string& device_label, 193 const std::string& vendor_name, 194 const std::string& product_name, 195 chromeos::DeviceType device_type, 196 uint64 device_size_in_bytes) { 197 if (error_code == chromeos::MOUNT_ERROR_NONE) { 198 disk_mount_manager_mock_->CreateDiskEntryForMountDevice( 199 mount_info, 200 unique_id, 201 device_label, 202 vendor_name, 203 product_name, 204 device_type, 205 device_size_in_bytes, 206 false /* is_parent */, 207 true /* has_media */, 208 false /* on_boot_device */, 209 true /* on_removable_device */); 210 } 211 monitor_->OnMountEvent(DiskMountManager::MOUNTING, error_code, mount_info); 212 WaitForFileThread(); 213 } 214 215 void StorageMonitorCrosTest::UnmountDevice( 216 chromeos::MountError error_code, 217 const DiskMountManager::MountPointInfo& mount_info) { 218 monitor_->OnMountEvent(DiskMountManager::UNMOUNTING, error_code, mount_info); 219 if (error_code == chromeos::MOUNT_ERROR_NONE) 220 disk_mount_manager_mock_->RemoveDiskEntryForMountDevice(mount_info); 221 WaitForFileThread(); 222 } 223 224 uint64 StorageMonitorCrosTest::GetDeviceStorageSize( 225 const std::string& device_location) { 226 StorageInfo info; 227 if (!monitor_->GetStorageInfoForPath(base::FilePath(device_location), &info)) 228 return 0; 229 230 return info.total_size_in_bytes(); 231 } 232 233 base::FilePath StorageMonitorCrosTest::CreateMountPoint( 234 const std::string& dir, bool with_dcim_dir) { 235 base::FilePath return_path(scoped_temp_dir_.path()); 236 return_path = return_path.AppendASCII(dir); 237 base::FilePath path(return_path); 238 if (with_dcim_dir) 239 path = path.Append(kDCIMDirectoryName); 240 if (!base::CreateDirectory(path)) 241 return base::FilePath(); 242 return return_path; 243 } 244 245 // static 246 void StorageMonitorCrosTest::PostQuitToUIThread() { 247 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 248 base::MessageLoop::QuitClosure()); 249 } 250 251 // static 252 void StorageMonitorCrosTest::WaitForFileThread() { 253 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 254 base::Bind(&PostQuitToUIThread)); 255 base::MessageLoop::current()->Run(); 256 } 257 258 void StorageMonitorCrosTest::EjectNotify(StorageMonitor::EjectStatus status) { 259 status_ = status; 260 } 261 262 // Simple test case where we attach and detach a media device. 263 TEST_F(StorageMonitorCrosTest, BasicAttachDetach) { 264 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true); 265 ASSERT_FALSE(mount_path1.empty()); 266 DiskMountManager::MountPointInfo mount_info( 267 kDevice1, 268 mount_path1.value(), 269 chromeos::MOUNT_TYPE_DEVICE, 270 chromeos::disks::MOUNT_CONDITION_NONE); 271 MountDevice(chromeos::MOUNT_ERROR_NONE, 272 mount_info, 273 kUniqueId1, 274 kDevice1Name, 275 kVendorName, 276 kProductName, 277 chromeos::DEVICE_TYPE_USB, 278 kDevice1SizeInBytes); 279 EXPECT_EQ(1, observer().attach_calls()); 280 EXPECT_EQ(0, observer().detach_calls()); 281 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 282 observer().last_attached().device_id()); 283 EXPECT_EQ(mount_path1.value(), observer().last_attached().location()); 284 285 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info); 286 EXPECT_EQ(1, observer().attach_calls()); 287 EXPECT_EQ(1, observer().detach_calls()); 288 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 289 observer().last_detached().device_id()); 290 291 base::FilePath mount_path2 = CreateMountPoint(kMountPointB, true); 292 ASSERT_FALSE(mount_path2.empty()); 293 DiskMountManager::MountPointInfo mount_info2( 294 kDevice2, 295 mount_path2.value(), 296 chromeos::MOUNT_TYPE_DEVICE, 297 chromeos::disks::MOUNT_CONDITION_NONE); 298 MountDevice(chromeos::MOUNT_ERROR_NONE, 299 mount_info2, 300 kUniqueId2, 301 kDevice2Name, 302 kVendorName, 303 kProductName, 304 chromeos::DEVICE_TYPE_USB, 305 kDevice2SizeInBytes); 306 EXPECT_EQ(2, observer().attach_calls()); 307 EXPECT_EQ(1, observer().detach_calls()); 308 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 309 observer().last_attached().device_id()); 310 EXPECT_EQ(mount_path2.value(), observer().last_attached().location()); 311 312 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2); 313 EXPECT_EQ(2, observer().attach_calls()); 314 EXPECT_EQ(2, observer().detach_calls()); 315 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 316 observer().last_detached().device_id()); 317 } 318 319 // Removable mass storage devices with no dcim folder are also recognized. 320 TEST_F(StorageMonitorCrosTest, NoDCIM) { 321 testing::Sequence mock_sequence; 322 base::FilePath mount_path = CreateMountPoint(kMountPointA, false); 323 const std::string kUniqueId = "FFFF-FFFF"; 324 ASSERT_FALSE(mount_path.empty()); 325 DiskMountManager::MountPointInfo mount_info( 326 kDevice1, 327 mount_path.value(), 328 chromeos::MOUNT_TYPE_DEVICE, 329 chromeos::disks::MOUNT_CONDITION_NONE); 330 const std::string device_id = StorageInfo::MakeDeviceId( 331 StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM, 332 kFSUniqueIdPrefix + kUniqueId); 333 MountDevice(chromeos::MOUNT_ERROR_NONE, 334 mount_info, 335 kUniqueId, 336 kDevice1Name, 337 kVendorName, 338 kProductName, 339 chromeos::DEVICE_TYPE_USB, 340 kDevice1SizeInBytes); 341 EXPECT_EQ(1, observer().attach_calls()); 342 EXPECT_EQ(0, observer().detach_calls()); 343 EXPECT_EQ(device_id, observer().last_attached().device_id()); 344 EXPECT_EQ(mount_path.value(), observer().last_attached().location()); 345 } 346 347 // Non device mounts and mount errors are ignored. 348 TEST_F(StorageMonitorCrosTest, Ignore) { 349 testing::Sequence mock_sequence; 350 base::FilePath mount_path = CreateMountPoint(kMountPointA, true); 351 const std::string kUniqueId = "FFFF-FFFF"; 352 ASSERT_FALSE(mount_path.empty()); 353 354 // Mount error. 355 DiskMountManager::MountPointInfo mount_info( 356 kDevice1, 357 mount_path.value(), 358 chromeos::MOUNT_TYPE_DEVICE, 359 chromeos::disks::MOUNT_CONDITION_NONE); 360 MountDevice(chromeos::MOUNT_ERROR_UNKNOWN, 361 mount_info, 362 kUniqueId, 363 kDevice1Name, 364 kVendorName, 365 kProductName, 366 chromeos::DEVICE_TYPE_USB, 367 kDevice1SizeInBytes); 368 EXPECT_EQ(0, observer().attach_calls()); 369 EXPECT_EQ(0, observer().detach_calls()); 370 371 // Not a device 372 mount_info.mount_type = chromeos::MOUNT_TYPE_ARCHIVE; 373 MountDevice(chromeos::MOUNT_ERROR_NONE, 374 mount_info, 375 kUniqueId, 376 kDevice1Name, 377 kVendorName, 378 kProductName, 379 chromeos::DEVICE_TYPE_USB, 380 kDevice1SizeInBytes); 381 EXPECT_EQ(0, observer().attach_calls()); 382 EXPECT_EQ(0, observer().detach_calls()); 383 384 // Unsupported file system. 385 mount_info.mount_type = chromeos::MOUNT_TYPE_DEVICE; 386 mount_info.mount_condition = 387 chromeos::disks::MOUNT_CONDITION_UNSUPPORTED_FILESYSTEM; 388 MountDevice(chromeos::MOUNT_ERROR_NONE, 389 mount_info, 390 kUniqueId, 391 kDevice1Name, 392 kVendorName, 393 kProductName, 394 chromeos::DEVICE_TYPE_USB, 395 kDevice1SizeInBytes); 396 EXPECT_EQ(0, observer().attach_calls()); 397 EXPECT_EQ(0, observer().detach_calls()); 398 } 399 400 TEST_F(StorageMonitorCrosTest, SDCardAttachDetach) { 401 base::FilePath mount_path1 = CreateMountPoint(kSDCardMountPoint1, true); 402 ASSERT_FALSE(mount_path1.empty()); 403 DiskMountManager::MountPointInfo mount_info1( 404 kSDCardDeviceName1, 405 mount_path1.value(), 406 chromeos::MOUNT_TYPE_DEVICE, 407 chromeos::disks::MOUNT_CONDITION_NONE); 408 MountDevice(chromeos::MOUNT_ERROR_NONE, 409 mount_info1, 410 kUniqueId2, 411 kSDCardDeviceName1, 412 kVendorName, 413 kProductName, 414 chromeos::DEVICE_TYPE_SD, 415 kSDCardSizeInBytes); 416 EXPECT_EQ(1, observer().attach_calls()); 417 EXPECT_EQ(0, observer().detach_calls()); 418 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 419 observer().last_attached().device_id()); 420 EXPECT_EQ(mount_path1.value(), observer().last_attached().location()); 421 422 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info1); 423 EXPECT_EQ(1, observer().attach_calls()); 424 EXPECT_EQ(1, observer().detach_calls()); 425 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 426 observer().last_detached().device_id()); 427 428 base::FilePath mount_path2 = CreateMountPoint(kSDCardMountPoint2, true); 429 ASSERT_FALSE(mount_path2.empty()); 430 DiskMountManager::MountPointInfo mount_info2( 431 kSDCardDeviceName2, 432 mount_path2.value(), 433 chromeos::MOUNT_TYPE_DEVICE, 434 chromeos::disks::MOUNT_CONDITION_NONE); 435 MountDevice(chromeos::MOUNT_ERROR_NONE, 436 mount_info2, 437 kUniqueId2, 438 kSDCardDeviceName2, 439 kVendorName, 440 kProductName, 441 chromeos::DEVICE_TYPE_SD, 442 kSDCardSizeInBytes); 443 EXPECT_EQ(2, observer().attach_calls()); 444 EXPECT_EQ(1, observer().detach_calls()); 445 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 446 observer().last_attached().device_id()); 447 EXPECT_EQ(mount_path2.value(), observer().last_attached().location()); 448 449 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info2); 450 EXPECT_EQ(2, observer().attach_calls()); 451 EXPECT_EQ(2, observer().detach_calls()); 452 EXPECT_EQ(GetDCIMDeviceId(kUniqueId2), 453 observer().last_detached().device_id()); 454 } 455 456 TEST_F(StorageMonitorCrosTest, AttachDeviceWithEmptyLabel) { 457 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true); 458 ASSERT_FALSE(mount_path1.empty()); 459 DiskMountManager::MountPointInfo mount_info( 460 kEmptyDeviceLabel, 461 mount_path1.value(), 462 chromeos::MOUNT_TYPE_DEVICE, 463 chromeos::disks::MOUNT_CONDITION_NONE); 464 MountDevice(chromeos::MOUNT_ERROR_NONE, 465 mount_info, 466 kUniqueId1, 467 kEmptyDeviceLabel, 468 kVendorName, 469 kProductName, 470 chromeos::DEVICE_TYPE_USB, 471 kDevice1SizeInBytes); 472 EXPECT_EQ(1, observer().attach_calls()); 473 EXPECT_EQ(0, observer().detach_calls()); 474 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 475 observer().last_attached().device_id()); 476 EXPECT_EQ(mount_path1.value(), observer().last_attached().location()); 477 478 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info); 479 EXPECT_EQ(1, observer().attach_calls()); 480 EXPECT_EQ(1, observer().detach_calls()); 481 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 482 observer().last_detached().device_id()); 483 } 484 485 TEST_F(StorageMonitorCrosTest, GetStorageSize) { 486 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true); 487 ASSERT_FALSE(mount_path1.empty()); 488 DiskMountManager::MountPointInfo mount_info( 489 kEmptyDeviceLabel, 490 mount_path1.value(), 491 chromeos::MOUNT_TYPE_DEVICE, 492 chromeos::disks::MOUNT_CONDITION_NONE); 493 MountDevice(chromeos::MOUNT_ERROR_NONE, 494 mount_info, 495 kUniqueId1, 496 kEmptyDeviceLabel, 497 kVendorName, 498 kProductName, 499 chromeos::DEVICE_TYPE_USB, 500 kDevice1SizeInBytes); 501 EXPECT_EQ(1, observer().attach_calls()); 502 EXPECT_EQ(0, observer().detach_calls()); 503 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 504 observer().last_attached().device_id()); 505 EXPECT_EQ(mount_path1.value(), observer().last_attached().location()); 506 507 EXPECT_EQ(kDevice1SizeInBytes, GetDeviceStorageSize(mount_path1.value())); 508 UnmountDevice(chromeos::MOUNT_ERROR_NONE, mount_info); 509 EXPECT_EQ(1, observer().attach_calls()); 510 EXPECT_EQ(1, observer().detach_calls()); 511 EXPECT_EQ(GetDCIMDeviceId(kUniqueId1), 512 observer().last_detached().device_id()); 513 } 514 515 void UnmountFake(const std::string& location, 516 chromeos::UnmountOptions options, 517 const DiskMountManager::UnmountPathCallback& cb) { 518 cb.Run(chromeos::MOUNT_ERROR_NONE); 519 } 520 521 TEST_F(StorageMonitorCrosTest, EjectTest) { 522 base::FilePath mount_path1 = CreateMountPoint(kMountPointA, true); 523 ASSERT_FALSE(mount_path1.empty()); 524 DiskMountManager::MountPointInfo mount_info( 525 kEmptyDeviceLabel, 526 mount_path1.value(), 527 chromeos::MOUNT_TYPE_DEVICE, 528 chromeos::disks::MOUNT_CONDITION_NONE); 529 MountDevice(chromeos::MOUNT_ERROR_NONE, 530 mount_info, 531 kUniqueId1, 532 kEmptyDeviceLabel, 533 kVendorName, 534 kProductName, 535 chromeos::DEVICE_TYPE_USB, 536 kDevice1SizeInBytes); 537 EXPECT_EQ(1, observer().attach_calls()); 538 EXPECT_EQ(0, observer().detach_calls()); 539 540 ON_CALL(*disk_mount_manager_mock_, UnmountPath(_, _, _)) 541 .WillByDefault(testing::Invoke(&UnmountFake)); 542 EXPECT_CALL(*disk_mount_manager_mock_, 543 UnmountPath(observer().last_attached().location(), _, _)); 544 monitor_->EjectDevice(observer().last_attached().device_id(), 545 base::Bind(&StorageMonitorCrosTest::EjectNotify, 546 base::Unretained(this))); 547 base::RunLoop().RunUntilIdle(); 548 549 EXPECT_EQ(StorageMonitor::EJECT_OK, status_); 550 } 551 552 } // namespace 553 554 } // namespace storage_monitor 555