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
23namespace storage_monitor {
24
25namespace {
26
27using content::BrowserThread;
28using chromeos::disks::DiskMountManager;
29using testing::_;
30
31const char kDevice1[] = "/dev/d1";
32const char kDevice1Name[] = "d1";
33const char kDevice2[] = "/dev/disk/d2";
34const char kDevice2Name[] = "d2";
35const char kEmptyDeviceLabel[] = "";
36const char kMountPointA[] = "mnt_a";
37const char kMountPointB[] = "mnt_b";
38const char kSDCardDeviceName1[] = "8.6 MB Amy_SD";
39const char kSDCardDeviceName2[] = "8.6 MB SD Card";
40const char kSDCardMountPoint1[] = "media/removable/Amy_SD";
41const char kSDCardMountPoint2[] = "media/removable/SD Card";
42const char kProductName[] = "Z101";
43const char kUniqueId1[] = "FFFF-FFFF";
44const char kUniqueId2[] = "FFFF-FF0F";
45const char kVendorName[] = "CompanyA";
46
47uint64 kDevice1SizeInBytes = 113048;
48uint64 kDevice2SizeInBytes = 212312;
49uint64 kSDCardSizeInBytes = 9000000;
50
51std::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.
58class 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.
91class 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
150StorageMonitorCrosTest::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
157StorageMonitorCrosTest::~StorageMonitorCrosTest() {
158}
159
160void 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
179void 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
188void 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
215void 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
224uint64 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
233base::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
246void StorageMonitorCrosTest::PostQuitToUIThread() {
247  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
248                          base::MessageLoop::QuitClosure());
249}
250
251// static
252void StorageMonitorCrosTest::WaitForFileThread() {
253  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
254                          base::Bind(&PostQuitToUIThread));
255  base::MessageLoop::current()->Run();
256}
257
258void StorageMonitorCrosTest::EjectNotify(StorageMonitor::EjectStatus status) {
259  status_ = status;
260}
261
262// Simple test case where we attach and detach a media device.
263TEST_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.
320TEST_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.
348TEST_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
400TEST_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
456TEST_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
485TEST_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
515void UnmountFake(const std::string& location,
516                 chromeos::UnmountOptions options,
517                 const DiskMountManager::UnmountPathCallback& cb) {
518  cb.Run(chromeos::MOUNT_ERROR_NONE);
519}
520
521TEST_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