diff options
author | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-29 21:33:03 +0000 |
---|---|---|
committer | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-29 21:33:03 +0000 |
commit | 7299ca59de7c9704c2302c680b43f70ce6916de3 (patch) | |
tree | 9107c50e964672c47742ed3c3e34a4a98d8aec0f | |
parent | 7102cd4997c97f6162e76c67384ecaeefb881e09 (diff) | |
download | chromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.zip chromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.tar.gz chromium_src-7299ca59de7c9704c2302c680b43f70ce6916de3.tar.bz2 |
Make the Linux System Monitor implementation track all devices
Add a few new functions to support media gallery and rename based on the new names in SystemMonitor
BUG=144496
Review URL: https://chromiumcodereview.appspot.com/10882039
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@153980 0039d316-1c4b-4281-b951-d872f2087c98
12 files changed, 1346 insertions, 1008 deletions
diff --git a/chrome/browser/chrome_browser_main_linux.cc b/chrome/browser/chrome_browser_main_linux.cc index ad21460..be3b048 100644 --- a/chrome/browser/chrome_browser_main_linux.cc +++ b/chrome/browser/chrome_browser_main_linux.cc @@ -5,7 +5,7 @@ #include "chrome/browser/chrome_browser_main_linux.h" #if !defined(OS_CHROMEOS) -#include "chrome/browser/media_gallery/media_device_notifications_linux.h" +#include "chrome/browser/media_gallery/removable_device_notifications_linux.h" #endif #if defined(USE_LINUX_BREAKPAD) @@ -104,9 +104,9 @@ void ChromeBrowserMainPartsLinux::PreProfileInit() { #if !defined(OS_CHROMEOS) const FilePath kDefaultMtabPath("/etc/mtab"); - media_device_notifications_linux_ = - new chrome::MediaDeviceNotificationsLinux(kDefaultMtabPath); - media_device_notifications_linux_->Init(); + removable_device_notifications_linux_ = + new chrome::RemovableDeviceNotificationsLinux(kDefaultMtabPath); + removable_device_notifications_linux_->Init(); #endif ChromeBrowserMainPartsPosix::PreProfileInit(); @@ -117,7 +117,7 @@ void ChromeBrowserMainPartsLinux::PostMainMessageLoopRun() { // Release it now. Otherwise the FILE thread would be gone when we try to // release it in the dtor and Valgrind would report a leak on almost ever // single browser_test. - media_device_notifications_linux_ = NULL; + removable_device_notifications_linux_ = NULL; #endif ChromeBrowserMainPartsPosix::PostMainMessageLoopRun(); diff --git a/chrome/browser/chrome_browser_main_linux.h b/chrome/browser/chrome_browser_main_linux.h index b1d1521..b968086 100644 --- a/chrome/browser/chrome_browser_main_linux.h +++ b/chrome/browser/chrome_browser_main_linux.h @@ -18,7 +18,7 @@ #if !defined(OS_CHROMEOS) namespace chrome { -class MediaDeviceNotificationsLinux; +class RemovableDeviceNotificationsLinux; } #endif @@ -34,8 +34,8 @@ class ChromeBrowserMainPartsLinux : public ChromeBrowserMainPartsPosix { private: #if !defined(OS_CHROMEOS) - scoped_refptr<chrome::MediaDeviceNotificationsLinux> - media_device_notifications_linux_; + scoped_refptr<chrome::RemovableDeviceNotificationsLinux> + removable_device_notifications_linux_; #endif #if defined(OS_CHROMEOS) diff --git a/chrome/browser/media_gallery/media_device_notifications_linux.cc b/chrome/browser/media_gallery/media_device_notifications_linux.cc deleted file mode 100644 index 1ab1566..0000000 --- a/chrome/browser/media_gallery/media_device_notifications_linux.cc +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright (c) 2012 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. - -// MediaDeviceNotificationsLinux implementation. - -#include "chrome/browser/media_gallery/media_device_notifications_linux.h" - -#include <libudev.h> -#include <mntent.h> -#include <stdio.h> - -#include <vector> - -#include "base/bind.h" -#include "base/file_path.h" -#include "base/memory/scoped_generic_obj.h" -#include "base/metrics/histogram.h" -#include "base/stl_util.h" -#include "base/string_number_conversions.h" -#include "base/string_util.h" -#include "base/stringprintf.h" -#include "base/system_monitor/system_monitor.h" -#include "base/utf_string_conversions.h" -#include "chrome/browser/media_gallery/media_device_notifications_utils.h" -#include "chrome/browser/media_gallery/media_gallery_constants.h" -#include "chrome/browser/media_gallery/media_storage_util.h" - -namespace chrome { - -using base::SystemMonitor; -using content::BrowserThread; - -namespace { - -// List of file systems we care about. -const char* const kKnownFileSystems[] = { - "ext2", - "ext3", - "ext4", - "fat", - "hfsplus", - "iso9660", - "msdos", - "ntfs", - "udf", - "vfat", -}; - -// udev device property constants. -const char kDevName[] = "DEVNAME"; -const char kFsUUID[] = "ID_FS_UUID"; -const char kLabel[] = "ID_FS_LABEL"; -const char kModel[] = "ID_MODEL"; -const char kModelID[] = "ID_MODEL_ID"; -const char kSerial[] = "ID_SERIAL"; -const char kSerialShort[] = "ID_SERIAL_SHORT"; -const char kVendor[] = "ID_VENDOR"; -const char kVendorID[] = "ID_VENDOR_ID"; - -// Device mount point details. -struct MountPointEntryInfo { - std::string mount_point; - int entry_pos; -}; - -// ScopedGenericObj functor for UdevObjectRelease(). -class ScopedReleaseUdevObject { - public: - void operator()(struct udev* udev) const { - udev_unref(udev); - } -}; -typedef ScopedGenericObj<struct udev*, - ScopedReleaseUdevObject> ScopedUdevObject; - -// ScopedGenericObj functor for UdevDeviceObjectRelease(). -class ScopedReleaseUdevDeviceObject { - public: - void operator()(struct udev_device* device) const { - udev_device_unref(device); - } -}; -typedef ScopedGenericObj<struct udev_device*, - ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject; - -// Wrapper function for udev_device_get_property_value() that also checks for -// valid but empty values. -std::string GetUdevDevicePropertyValue(struct udev_device* udev_device, - const char* key) { - const char* value = udev_device_get_property_value(udev_device, key); - if (!value) - return std::string(); - return (strlen(value) > 0) ? value : std::string(); -} - -// Get the device information using udev library. -// On success, returns true and fill in |id| and |name|. -bool GetDeviceInfo(const std::string& device_path, std::string* id, - string16* name) { - DCHECK(!device_path.empty()); - - ScopedUdevObject udev_obj(udev_new()); - if (!udev_obj.get()) - return false; - - struct stat device_stat; - if (stat(device_path.c_str(), &device_stat) < 0) - return false; - - char device_type; - if (S_ISCHR(device_stat.st_mode)) - device_type = 'c'; - else if (S_ISBLK(device_stat.st_mode)) - device_type = 'b'; - else - return false; // Not a supported type. - - ScopedUdevDeviceObject device( - udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev)); - if (!device.get()) - return false; - - // Construct a device name using label or manufacturer (vendor and model) - // details. - if (name) { - std::string device_label = GetUdevDevicePropertyValue(device, kLabel); - if (device_label.empty()) - device_label = udev_device_get_property_value(device, kSerial); - if (device_label.empty()) { - // Format: VendorInfo ModelInfo - // E.g.: KnCompany Model2010 - std::string vendor_name = GetUdevDevicePropertyValue(device, kVendor); - std::string model_name = GetUdevDevicePropertyValue(device, kModel); - if (vendor_name.empty() && model_name.empty()) - return false; - - if (vendor_name.empty()) - device_label = model_name; - else if (model_name.empty()) - device_label = vendor_name; - else - device_label = vendor_name + kSpaceDelim + model_name; - } - - if (IsStringUTF8(device_label)) - *name = UTF8ToUTF16(device_label); - } - - // Construct a device id using label or manufacturer (vendor and model) - // details. - if (id) { - std::string unique_id = GetUdevDevicePropertyValue(device, kFsUUID); - if (unique_id.empty()) { - // If one of the vendor, model, serial information is missing, its value - // in the string is empty. - // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo - // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB - std::string vendor = GetUdevDevicePropertyValue(device, kVendorID); - std::string model = GetUdevDevicePropertyValue(device, kModelID); - std::string serial_short = GetUdevDevicePropertyValue(device, - kSerialShort); - if (vendor.empty() && model.empty() && serial_short.empty()) - return false; - - unique_id = base::StringPrintf("%s%s%s%s%s%s", - kVendorModelSerialPrefix, - vendor.c_str(), - kNonSpaceDelim, - model.c_str(), - kNonSpaceDelim, - serial_short.c_str()); - } else { - unique_id = kFSUniqueIdPrefix + unique_id; - } - *id = MediaStorageUtil::MakeDeviceId( - MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM, unique_id); - } - return true; -} - -} // namespace - -MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux( - const FilePath& path) - : initialized_(false), - mtab_path_(path), - get_device_info_func_(&GetDeviceInfo) { -} - -MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux( - const FilePath& path, - GetDeviceInfoFunc get_device_info_func) - : initialized_(false), - mtab_path_(path), - get_device_info_func_(get_device_info_func) { -} - -MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); -} - -void MediaDeviceNotificationsLinux::Init() { - DCHECK(!mtab_path_.empty()); - - // Put |kKnownFileSystems| in std::set to get O(log N) access time. - for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) - known_file_systems_.insert(kKnownFileSystems[i]); - - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(&MediaDeviceNotificationsLinux::InitOnFileThread, this)); -} - -void MediaDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path, - bool error) { - if (path != mtab_path_) { - // This cannot happen unless FilePathWatcher is buggy. Just ignore this - // notification and do nothing. - NOTREACHED(); - return; - } - if (error) { - LOG(ERROR) << "Error watching " << mtab_path_.value(); - return; - } - - UpdateMtab(); -} - -void MediaDeviceNotificationsLinux::InitOnFileThread() { - DCHECK(!initialized_); - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - initialized_ = true; - - // The callback passed to Watch() has to be unretained. Otherwise - // MediaDeviceNotificationsLinux will live longer than expected, and - // FilePathWatcher will get in trouble at shutdown time. - bool ret = file_watcher_.Watch( - mtab_path_, - base::Bind(&MediaDeviceNotificationsLinux::OnFilePathChanged, - base::Unretained(this))); - if (!ret) { - LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed"; - return; - } - - UpdateMtab(); -} - -void MediaDeviceNotificationsLinux::UpdateMtab() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - - MountPointDeviceMap new_mtab; - ReadMtab(&new_mtab); - - // Check existing mtab entries for unaccounted mount points. - // These mount points must have been removed in the new mtab. - std::vector<std::string> mount_points_to_erase; - for (MountMap::const_iterator old_iter = mount_info_map_.begin(); - old_iter != mount_info_map_.end(); ++old_iter) { - const std::string& mount_point = old_iter->first; - const std::string& mount_device = old_iter->second.mount_device; - MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point); - // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at - // |mount_point|. - if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) { - RemoveOldDevice(old_iter->second.device_id); - mount_points_to_erase.push_back(mount_point); - } - } - // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above - // using the iterator is slightly more efficient, but more tricky, since - // calling std::map::erase() on an iterator invalidates it. - for (size_t i = 0; i < mount_points_to_erase.size(); ++i) - mount_info_map_.erase(mount_points_to_erase[i]); - - // Check new mtab entries against existing ones. - for (MountPointDeviceMap::iterator new_iter = new_mtab.begin(); - new_iter != new_mtab.end(); ++new_iter) { - const std::string& mount_point = new_iter->first; - const std::string& mount_device = new_iter->second; - MountMap::iterator old_iter = mount_info_map_.find(mount_point); - if (old_iter == mount_info_map_.end() || - old_iter->second.mount_device != mount_device) { - // New mount point found or an existing mount point found with a new - // device. - CheckAndAddMediaDevice(mount_device, mount_point); - } - } -} - -void MediaDeviceNotificationsLinux::ReadMtab(MountPointDeviceMap* mtab) { - FILE* fp = setmntent(mtab_path_.value().c_str(), "r"); - if (!fp) - return; - - mntent entry; - char buf[512]; - - // Keep track of mount point entry positions in mtab file. - int entry_pos = 0; - - // Helper map to store the device mount point details. - // (mount device, MountPointEntryInfo) - typedef std::map<std::string, MountPointEntryInfo> DeviceMap; - DeviceMap device_map; - while (getmntent_r(fp, &entry, buf, sizeof(buf))) { - // We only care about real file systems. - if (!ContainsKey(known_file_systems_, entry.mnt_type)) - continue; - - // Add entries, but overwrite entries for the same mount device. Keep track - // of the entry positions in |entry_info| and use that below to resolve - // multiple devices mounted at the same mount point. - MountPointEntryInfo entry_info; - entry_info.mount_point = entry.mnt_dir; - entry_info.entry_pos = entry_pos++; - device_map[entry.mnt_fsname] = entry_info; - } - endmntent(fp); - - // Helper map to store mount point entries. - // (mount point, entry position in mtab file) - typedef std::map<std::string, int> MountPointsInfoMap; - MountPointsInfoMap mount_points_info_map; - MountPointDeviceMap& new_mtab = *mtab; - for (DeviceMap::const_iterator device_it = device_map.begin(); - device_it != device_map.end(); - ++device_it) { - // Add entries, but overwrite entries for the same mount point. Keep track - // of the entry positions and use that information to resolve multiple - // devices mounted at the same mount point. - const std::string& mount_device = device_it->first; - const std::string& mount_point = device_it->second.mount_point; - const int entry_pos = device_it->second.entry_pos; - MountPointDeviceMap::iterator new_it = new_mtab.find(mount_point); - MountPointsInfoMap::iterator mount_point_info_map_it = - mount_points_info_map.find(mount_point); - - // New mount point entry found or there is already a device mounted at - // |mount_point| and the existing mount entry is older than the current one. - if (new_it == new_mtab.end() || - (mount_point_info_map_it != mount_points_info_map.end() && - mount_point_info_map_it->second < entry_pos)) { - new_mtab[mount_point] = mount_device; - mount_points_info_map[mount_point] = entry_pos; - } - } -} - -void MediaDeviceNotificationsLinux::CheckAndAddMediaDevice( - const std::string& mount_device, - const std::string& mount_point) { - if (!IsMediaDevice(mount_point)) - return; - - std::string id; - string16 name; - bool result = get_device_info_func_(mount_device, &id, &name); - - // Keep track of GetDeviceInfo result, to see how often we fail to get device - // details. - UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available", - result); - if (!result) - return; - - // Keep track of device uuid, to see how often we receive empty values. - UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available", - !id.empty()); - UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available", - !name.empty()); - - if (id.empty() || name.empty()) - return; - - MountDeviceAndId mount_device_and_id; - mount_device_and_id.mount_device = mount_device; - mount_device_and_id.device_id = id; - mount_info_map_[mount_point] = mount_device_and_id; - - SystemMonitor::Get()->ProcessRemovableStorageAttached(id, name, mount_point); -} - -void MediaDeviceNotificationsLinux::RemoveOldDevice( - const std::string& device_id) { - SystemMonitor::Get()->ProcessRemovableStorageDetached(device_id); -} - -} // namespace chrome diff --git a/chrome/browser/media_gallery/media_device_notifications_linux.h b/chrome/browser/media_gallery/media_device_notifications_linux.h deleted file mode 100644 index 418c1e4..0000000 --- a/chrome/browser/media_gallery/media_device_notifications_linux.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2012 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. - -// MediaDeviceNotificationsLinux listens for mount point changes and notifies -// the SystemMonitor about the addition and deletion of media devices. - -#ifndef CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ -#define CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ - -#if defined(OS_CHROMEOS) -#error "Use the ChromeOS-specific implementation instead." -#endif - -#include <map> -#include <set> -#include <string> -#include <utility> - -#include "base/basictypes.h" -#include "base/compiler_specific.h" -#include "base/files/file_path_watcher.h" -#include "base/memory/ref_counted.h" -#include "content/public/browser/browser_thread.h" - -class FilePath; - -// Gets the media device information given a |device_path|. On success, -// returns true and fills in |name| and |id|. -typedef bool (*GetDeviceInfoFunc)(const std::string& device_path, - std::string* id, - string16* name); - -namespace chrome { - -class MediaDeviceNotificationsLinux - : public base::RefCountedThreadSafe<MediaDeviceNotificationsLinux, - content::BrowserThread::DeleteOnFileThread> { - public: - explicit MediaDeviceNotificationsLinux(const FilePath& path); - - // Must be called for MediaDeviceNotificationsLinux to work. - void Init(); - - protected: - // Only for use in unit tests. - MediaDeviceNotificationsLinux(const FilePath& path, - GetDeviceInfoFunc getDeviceInfo); - - // Avoids code deleting the object while there are references to it. - // Aside from the base::RefCountedThreadSafe friend class, and derived - // classes, any attempts to call this dtor will result in a compile-time - // error. - virtual ~MediaDeviceNotificationsLinux(); - - virtual void OnFilePathChanged(const FilePath& path, bool error); - - private: - friend class base::RefCountedThreadSafe<MediaDeviceNotificationsLinux>; - friend class base::DeleteHelper<MediaDeviceNotificationsLinux>; - friend struct content::BrowserThread::DeleteOnThread< - content::BrowserThread::FILE>; - - // Structure to save mounted device information such as device path and unique - // identifier. - struct MountDeviceAndId { - std::string mount_device; - std::string device_id; - }; - - // Mapping of mount points to MountDeviceAndId. - typedef std::map<std::string, MountDeviceAndId> MountMap; - - // (mount point, mount device) - // Helper Map to get new entries from mtab file. - typedef std::map<std::string, std::string> MountPointDeviceMap; - - // Do initialization on the File Thread. - void InitOnFileThread(); - - // Parses mtab file and find all changes. - void UpdateMtab(); - - // Reads mtab file entries into |mtab|. - void ReadMtab(MountPointDeviceMap* mtab); - - // Checks and adds |mount_device| as media device given the |mount_point|. - void CheckAndAddMediaDevice(const std::string& mount_device, - const std::string& mount_point); - - // Removes media device with a given device id. - void RemoveOldDevice(const std::string& device_id); - - // Whether Init() has been called or not. - bool initialized_; - - // Mtab file that lists the mount points. - const FilePath mtab_path_; - - // Watcher for |mtab_path_|. - base::files::FilePathWatcher file_watcher_; - - // Mapping of relevant mount points and their corresponding mount devices. - // Keep in mind on Linux, a device can be mounted at multiple mount points, - // and multiple devices can be mounted at a mount point. - MountMap mount_info_map_; - - // Set of known file systems that we care about. - std::set<std::string> known_file_systems_; - - // Function handler to get device information. This is useful to set a mock - // handler for unit testing. - GetDeviceInfoFunc get_device_info_func_; - - DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinux); -}; - -} // namespace chrome - -#endif // CHROME_BROWSER_MEDIA_GALLERY_MEDIA_DEVICE_NOTIFICATIONS_LINUX_H_ diff --git a/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc b/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc deleted file mode 100644 index 1216d15..0000000 --- a/chrome/browser/media_gallery/media_device_notifications_linux_unittest.cc +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright (c) 2012 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. - -// MediaDeviceNotificationsLinux unit tests. - -#include "chrome/browser/media_gallery/media_device_notifications_linux.h" - -#include <mntent.h> -#include <stdio.h> - -#include <string> - -#include "base/file_util.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop.h" -#include "base/scoped_temp_dir.h" -#include "base/system_monitor/system_monitor.h" -#include "base/test/mock_devices_changed_observer.h" -#include "base/utf_string_conversions.h" -#include "chrome/browser/media_gallery/media_storage_util.h" -#include "content/public/test/test_browser_thread.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace chrome { - -namespace { - -using testing::_; - -const char kValidFS[] = "vfat"; -const char kInvalidFS[] = "invalidfs"; - -const char kInvalidPath[] = "invalid path does not exist"; - -const char kDevice1[] = "d1"; -const char kDevice2[] = "d2"; -const char kDevice3[] = "d3"; - -const char kDeviceId1[] = "UUID:FFF0-000F"; -const char kDeviceId2[] = "VendorModelSerial:ComName:Model2010:898989898989"; -const char kDeviceId3[] = "VendorModelSerial:::WEM319X792"; - -const char kDeviceLabel1[] = "TEST_USB_MODEL_1"; -const char kDeviceLabel2[] = "TEST_USB_MODEL_2"; -const char kDeviceLabel3[] = "TEST_USB_MODEL_3"; - -const char kMountPointA[] = "mnt_a"; -const char kMountPointB[] = "mnt_b"; - -std::string GetDCIMDeviceId(std::string unique_id) { - return chrome::MediaStorageUtil::MakeDeviceId( - chrome::MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM, unique_id); -} - -bool GetDeviceInfo(const std::string& dev_path, std::string* id, - string16* name) { - std::string device_name; - if (dev_path == kDevice1) { - *id = GetDCIMDeviceId(kDeviceId1); - device_name = kDeviceLabel1; - } else if (dev_path == kDevice2) { - *id = GetDCIMDeviceId(kDeviceId2); - device_name = kDeviceLabel2; - } else if (dev_path == kDevice3) { - *id = GetDCIMDeviceId(kDeviceId3); - device_name = kDeviceLabel3; - } else { - return false; - } - - *name = ASCIIToUTF16(device_name); - return true; -} - -class MediaDeviceNotificationsLinuxTestWrapper - : public MediaDeviceNotificationsLinux { - public: - MediaDeviceNotificationsLinuxTestWrapper(const FilePath& path, - MessageLoop* message_loop) - : MediaDeviceNotificationsLinux(path, &GetDeviceInfo), - message_loop_(message_loop) { - } - - private: - // Avoids code deleting the object while there are references to it. - // Aside from the base::RefCountedThreadSafe friend class, any attempts to - // call this dtor will result in a compile-time error. - ~MediaDeviceNotificationsLinuxTestWrapper() {} - - virtual void OnFilePathChanged(const FilePath& path, bool error) OVERRIDE { - MediaDeviceNotificationsLinux::OnFilePathChanged(path, error); - message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); - } - - MessageLoop* message_loop_; - - DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTestWrapper); -}; - -class MediaDeviceNotificationsLinuxTest : public testing::Test { - public: - struct MtabTestData { - MtabTestData(const std::string& mount_device, - const std::string& mount_point, - const std::string& mount_type) - : mount_device(mount_device), - mount_point(mount_point), - mount_type(mount_type) { - } - - const std::string mount_device; - const std::string mount_point; - const std::string mount_type; - }; - - MediaDeviceNotificationsLinuxTest() - : message_loop_(MessageLoop::TYPE_IO), - file_thread_(content::BrowserThread::FILE, &message_loop_) { - } - virtual ~MediaDeviceNotificationsLinuxTest() {} - - protected: - virtual void SetUp() OVERRIDE { - mock_devices_changed_observer_.reset(new base::MockDevicesChangedObserver); - system_monitor_.AddDevicesChangedObserver( - mock_devices_changed_observer_.get()); - - // Create and set up a temp dir with files for the test. - ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); - FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc"); - ASSERT_TRUE(file_util::CreateDirectory(test_dir)); - mtab_file_ = test_dir.AppendASCII("test_mtab"); - MtabTestData initial_test_data[] = { - MtabTestData("dummydevice", "dummydir", kInvalidFS), - }; - WriteToMtab(initial_test_data, - arraysize(initial_test_data), - true /* overwrite */); - - // Initialize the test subject. - notifications_ = - new MediaDeviceNotificationsLinuxTestWrapper(mtab_file_, - &message_loop_); - notifications_->Init(); - message_loop_.RunAllPending(); - } - - virtual void TearDown() OVERRIDE { - message_loop_.RunAllPending(); - notifications_ = NULL; - system_monitor_.RemoveDevicesChangedObserver( - mock_devices_changed_observer_.get()); - } - - // Append mtab entries from the |data| array of size |data_size| to the mtab - // file, and run the message loop. - void AppendToMtabAndRunLoop(const MtabTestData* data, size_t data_size) { - WriteToMtab(data, data_size, false /* do not overwrite */); - message_loop_.Run(); - } - - // Overwrite the mtab file with mtab entries from the |data| array of size - // |data_size|, and run the message loop. - void OverwriteMtabAndRunLoop(const MtabTestData* data, size_t data_size) { - WriteToMtab(data, data_size, true /* overwrite */); - message_loop_.Run(); - } - - // Simplied version of OverwriteMtabAndRunLoop() that just deletes all the - // entries in the mtab file. - void WriteEmptyMtabAndRunLoop() { - OverwriteMtabAndRunLoop(NULL, // No data. - 0); // No data length. - } - - // Create a directory named |dir| relative to the test directory. - // It has a DCIM directory, so MediaDeviceNotificationsLinux recognizes it as - // a media directory. - FilePath CreateMountPointWithDCIMDir(const std::string& dir) { - return CreateMountPoint(dir, true /* create DCIM dir */); - } - - // Create a directory named |dir| relative to the test directory. - // It does not have a DCIM directory, so MediaDeviceNotificationsLinux does - // not recognizes it as a media directory. - FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) { - return CreateMountPoint(dir, false /* do not create DCIM dir */); - } - - base::MockDevicesChangedObserver& observer() { - return *mock_devices_changed_observer_; - } - - private: - // 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. - FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir) { - FilePath return_path(scoped_temp_dir_.path()); - return_path = return_path.AppendASCII(dir); - FilePath path(return_path); - if (with_dcim_dir) - path = path.AppendASCII("DCIM"); - if (!file_util::CreateDirectory(path)) - return FilePath(); - return return_path; - } - - // Write the test mtab data to |mtab_file_|. - // |data| is an array of mtab entries. - // |data_size| is the array size of |data|. - // |overwrite| specifies whether to overwrite |mtab_file_|. - void WriteToMtab(const MtabTestData* data, - size_t data_size, - bool overwrite) { - FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a"); - ASSERT_TRUE(file); - - // Due to the glibc *mntent() interface design, which is out of our - // control, the mtnent struct has several char* fields, even though - // addmntent() does not write to them in the calls below. To make the - // compiler happy while avoiding making additional copies of strings, - // we just const_cast() the strings' c_str()s. - // Assuming addmntent() does not write to the char* fields, this is safe. - // It is unlikely the platforms this test suite runs on will have an - // addmntent() implementation that does change the char* fields. If that - // was ever the case, the test suite will start crashing or failing. - mntent entry; - static const char kMountOpts[] = "rw"; - entry.mnt_opts = const_cast<char*>(kMountOpts); - entry.mnt_freq = 0; - entry.mnt_passno = 0; - for (size_t i = 0; i < data_size; ++i) { - entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str()); - entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str()); - entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str()); - ASSERT_EQ(0, addmntent(file, &entry)); - } - ASSERT_EQ(1, endmntent(file)); - } - - // The message loop and file thread to run tests on. - MessageLoop message_loop_; - content::TestBrowserThread file_thread_; - - // SystemMonitor and DevicesChangedObserver to hook together to test. - base::SystemMonitor system_monitor_; - scoped_ptr<base::MockDevicesChangedObserver> mock_devices_changed_observer_; - - // Temporary directory for created test data. - ScopedTempDir scoped_temp_dir_; - // Path to the test mtab file. - FilePath mtab_file_; - - scoped_refptr<MediaDeviceNotificationsLinuxTestWrapper> notifications_; - - DISALLOW_COPY_AND_ASSIGN(MediaDeviceNotificationsLinuxTest); -}; - -// Simple test case where we attach and detach a media device. -TEST_F(MediaDeviceNotificationsLinuxTest, BasicAttachDetach) { - testing::Sequence mock_sequence; - FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA); - ASSERT_FALSE(test_path.empty()); - MtabTestData test_data[] = { - MtabTestData(kDevice1, kInvalidPath, kValidFS), - MtabTestData(kDevice2, test_path.value(), kValidFS), - }; - // Only |kDevice2| should be attached, since |kDevice1| has a bad path. - EXPECT_CALL(observer(), - OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId2), - ASCIIToUTF16(kDeviceLabel2), - test_path.value())) - .InSequence(mock_sequence); - AppendToMtabAndRunLoop(test_data, arraysize(test_data)); - - // |kDevice2| should be detached here. - EXPECT_CALL(observer(), - OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId2))) - .InSequence(mock_sequence); - WriteEmptyMtabAndRunLoop(); -} - -// Only mount points with DCIM directories are recognized. -TEST_F(MediaDeviceNotificationsLinuxTest, DCIM) { - testing::Sequence mock_sequence; - FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); - ASSERT_FALSE(test_path_a.empty()); - MtabTestData test_data1[] = { - MtabTestData(kDevice1, test_path_a.value(), kValidFS), - }; - // |kDevice1| should be attached as expected. - EXPECT_CALL(observer(), - OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1), - ASCIIToUTF16(kDeviceLabel1), - test_path_a.value())) - .InSequence(mock_sequence); - AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); - - // This should do nothing, since |kMountPointB| does not have a DCIM dir. - FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); - ASSERT_FALSE(test_path_b.empty()); - MtabTestData test_data2[] = { - MtabTestData(kDevice2, test_path_b.value(), kValidFS), - }; - AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); - - // |kDevice1| should be detached as expected. - EXPECT_CALL(observer(), - OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1))) - .InSequence(mock_sequence); - WriteEmptyMtabAndRunLoop(); -} - -// More complicated test case with multiple devices on multiple mount points. -TEST_F(MediaDeviceNotificationsLinuxTest, SwapMountPoints) { - FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); - FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); - ASSERT_FALSE(test_path_a.empty()); - ASSERT_FALSE(test_path_b.empty()); - - // Attach two devices. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - MtabTestData test_data1[] = { - MtabTestData(kDevice1, test_path_a.value(), kValidFS), - MtabTestData(kDevice2, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); - AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); - - // Detach two devices from old mount points and attach the devices at new - // mount points. - // kDevice1 -> kMountPointB - // kDevice2 -> kMountPointA - MtabTestData test_data2[] = { - MtabTestData(kDevice1, test_path_b.value(), kValidFS), - MtabTestData(kDevice2, test_path_a.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); - OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); - - // Detach all devices. - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); - WriteEmptyMtabAndRunLoop(); -} - -// More complicated test case with multiple devices on multiple mount points. -TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesMultiMountPoints) { - FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); - FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); - ASSERT_FALSE(test_path_a.empty()); - ASSERT_FALSE(test_path_b.empty()); - - // Attach two devices. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - MtabTestData test_data1[] = { - MtabTestData(kDevice1, test_path_a.value(), kValidFS), - MtabTestData(kDevice2, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); - AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); - - // Attach |kDevice1| to |kMountPointB|. - // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been - // re-attached at |kMountPointB|, so it is 'detached' from kMountPointA. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - // kDevice1 -> kMountPointB - MtabTestData test_data2[] = { - MtabTestData(kDevice1, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); - AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); - - // Attach |kDevice2| to |kMountPointA|. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - // kDevice1 -> kMountPointB - // kDevice2 -> kMountPointA - MtabTestData test_data3[] = { - MtabTestData(kDevice2, test_path_a.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); - AppendToMtabAndRunLoop(test_data3, arraysize(test_data3)); - - // Detach |kDevice2| from |kMountPointA|. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - // kDevice1 -> kMountPointB - MtabTestData test_data4[] = { - MtabTestData(kDevice1, test_path_a.value(), kValidFS), - MtabTestData(kDevice2, test_path_b.value(), kValidFS), - MtabTestData(kDevice1, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); - OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); - - // Detach |kDevice1| from |kMountPointB|. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); - OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); - - // Detach all devices. - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); - WriteEmptyMtabAndRunLoop(); -} - -// More complicated test case with multiple devices on one mount point. -TEST_F(MediaDeviceNotificationsLinuxTest, MultiDevicesOneMountPoint) { - FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); - FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); - ASSERT_FALSE(test_path_a.empty()); - ASSERT_FALSE(test_path_b.empty()); - - // |kDevice1| is most recently mounted at |kMountPointB|. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - // kDevice1 -> kMountPointB - MtabTestData test_data1[] = { - MtabTestData(kDevice1, test_path_a.value(), kValidFS), - MtabTestData(kDevice2, test_path_b.value(), kValidFS), - MtabTestData(kDevice1, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), - OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId1), - ASCIIToUTF16(kDeviceLabel1), - test_path_b.value())) - .Times(1); - EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); - OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); - - // Attach |kDevice3| to |kMountPointB|. - // |kDevice1| is inaccessible at its most recent mount point, so it is - // detached and unavailable, even though it is still accessible via - // |kMountPointA|. - // kDevice1 -> kMountPointA - // kDevice2 -> kMountPointB - // kDevice1 -> kMountPointB - // kDevice3 -> kMountPointB - MtabTestData test_data2[] = { - MtabTestData(kDevice3, test_path_b.value(), kValidFS), - }; - EXPECT_CALL(observer(), - OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId1))) - .Times(1); - EXPECT_CALL(observer(), - OnRemovableStorageAttached(GetDCIMDeviceId(kDeviceId3), - ASCIIToUTF16(kDeviceLabel3), - test_path_b.value())) - .Times(1); - AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); - - // Detach all devices. - EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); - EXPECT_CALL(observer(), - OnRemovableStorageDetached(GetDCIMDeviceId(kDeviceId3))) - .Times(1); - WriteEmptyMtabAndRunLoop(); -} - -} // namespace - -} // namespace chrome diff --git a/chrome/browser/media_gallery/media_storage_util.cc b/chrome/browser/media_gallery/media_storage_util.cc index 14dece0..ec9c477 100644 --- a/chrome/browser/media_gallery/media_storage_util.cc +++ b/chrome/browser/media_gallery/media_storage_util.cc @@ -156,7 +156,7 @@ void MediaStorageUtil::IsDeviceAttached(const std::string& device_id, callback.Run(!FindRemovableStorageLocationById(device_id).empty()); break; case REMOVABLE_MASS_STORAGE_NO_DCIM: - FindUSBDeviceById(unique_id, + FindUSBDeviceById(device_id, base::Bind(&EmptyPathIsFalseCallback, callback)); break; case FIXED_MASS_STORAGE: @@ -202,7 +202,7 @@ void MediaStorageUtil::FindDevicePathById(const std::string& device_id, callback.Run(FilePath()); break; case REMOVABLE_MASS_STORAGE_NO_DCIM: - FindUSBDeviceById(unique_id, callback); + FindUSBDeviceById(device_id, callback); break; case FIXED_MASS_STORAGE: // For this type, the unique_id is the path. @@ -222,7 +222,7 @@ void MediaStorageUtil::FindDevicePathById(const std::string& device_id, MediaStorageUtil::MediaStorageUtil() {} // static -void MediaStorageUtil::FindUSBDeviceById(const std::string& unique_id, +void MediaStorageUtil::FindUSBDeviceById(const std::string& device_id, const FilePathCallback& callback) { // TODO(vandebo) This needs to be implemented per platform. // Type is REMOVABLE_MASS_STORAGE_NO_DCIM, so it's a device possibly mounted diff --git a/chrome/browser/media_gallery/media_storage_util.h b/chrome/browser/media_gallery/media_storage_util.h index 3abff89..687044e 100644 --- a/chrome/browser/media_gallery/media_storage_util.h +++ b/chrome/browser/media_gallery/media_storage_util.h @@ -72,7 +72,7 @@ class MediaStorageUtil { MediaStorageUtil(); // A platform specific helper. - static void FindUSBDeviceById(const std::string& unique_id, + static void FindUSBDeviceById(const std::string& device_id, const FilePathCallback& callback); DISALLOW_COPY_AND_ASSIGN(MediaStorageUtil); diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux.cc b/chrome/browser/media_gallery/removable_device_notifications_linux.cc new file mode 100644 index 0000000..def022a --- /dev/null +++ b/chrome/browser/media_gallery/removable_device_notifications_linux.cc @@ -0,0 +1,512 @@ +// Copyright (c) 2012 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. + +// RemovableDeviceNotificationsLinux implementation. + +#include "chrome/browser/media_gallery/removable_device_notifications_linux.h" + +#include <libudev.h> +#include <mntent.h> +#include <stdio.h> + +#include <list> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/memory/scoped_generic_obj.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/system_monitor/system_monitor.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/media_gallery/media_device_notifications_utils.h" +#include "chrome/browser/media_gallery/media_gallery_constants.h" +#include "chrome/browser/media_gallery/media_storage_util.h" + +namespace chrome { + +using base::SystemMonitor; +using content::BrowserThread; + +namespace { + +static RemovableDeviceNotificationsLinux* + g_removable_device_notifications_linux = NULL; + +// List of file systems we care about. +const char* const kKnownFileSystems[] = { + "ext2", + "ext3", + "ext4", + "fat", + "hfsplus", + "iso9660", + "msdos", + "ntfs", + "udf", + "vfat", +}; + +// udev device property constants. +const char kBlockSubsystemKey[] = "block"; +const char kDevName[] = "DEVNAME"; +const char kDiskDeviceTypeKey[] = "disk"; +const char kFsUUID[] = "ID_FS_UUID"; +const char kLabel[] = "ID_FS_LABEL"; +const char kModel[] = "ID_MODEL"; +const char kModelID[] = "ID_MODEL_ID"; +const char kRemovableSysAttr[] = "removable"; +const char kSerial[] = "ID_SERIAL"; +const char kSerialShort[] = "ID_SERIAL_SHORT"; +const char kVendor[] = "ID_VENDOR"; +const char kVendorID[] = "ID_VENDOR_ID"; + +// (mount point, mount device) +// A mapping from mount point to mount device, as extracted from the mtab +// file. +typedef std::map<FilePath, FilePath> MountPointDeviceMap; + +// Reads mtab file entries into |mtab|. +void ReadMtab(const FilePath& mtab_path, + const std::set<std::string>& interesting_file_systems, + MountPointDeviceMap* mtab) { + mtab->clear(); + + FILE* fp = setmntent(mtab_path.value().c_str(), "r"); + if (!fp) + return; + + mntent entry; + char buf[512]; + + // We return the same device mounted to multiple locations, but hide + // devices that have been mounted over. + while (getmntent_r(fp, &entry, buf, sizeof(buf))) { + // We only care about real file systems. + if (!ContainsKey(interesting_file_systems, entry.mnt_type)) + continue; + + (*mtab)[FilePath(entry.mnt_dir)] = FilePath(entry.mnt_fsname); + } + endmntent(fp); +} + +// ScopedGenericObj functor for UdevObjectRelease(). +class ScopedReleaseUdevObject { + public: + void operator()(struct udev* udev) const { + udev_unref(udev); + } +}; +typedef ScopedGenericObj<struct udev*, + ScopedReleaseUdevObject> ScopedUdevObject; + +// ScopedGenericObj functor for UdevDeviceObjectRelease(). +class ScopedReleaseUdevDeviceObject { + public: + void operator()(struct udev_device* device) const { + udev_device_unref(device); + } +}; +typedef ScopedGenericObj<struct udev_device*, + ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject; + +// Wrapper function for udev_device_get_property_value() that also checks for +// valid but empty values. +std::string GetUdevDevicePropertyValue(struct udev_device* udev_device, + const char* key) { + const char* value = udev_device_get_property_value(udev_device, key); + if (!value) + return std::string(); + return (strlen(value) > 0) ? value : std::string(); +} + +// Construct a device id using label or manufacturer (vendor and model) details. +std::string MakeDeviceUniqueId(struct udev_device* device) { + std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID); + if (!uuid.empty()) + return kFSUniqueIdPrefix + uuid; + + // If one of the vendor, model, serial information is missing, its value + // in the string is empty. + // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo + // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB + std::string vendor = GetUdevDevicePropertyValue(device, kVendorID); + std::string model = GetUdevDevicePropertyValue(device, kModelID); + std::string serial_short = GetUdevDevicePropertyValue(device, + kSerialShort); + if (vendor.empty() && model.empty() && serial_short.empty()) + return std::string(); + + return base::StringPrintf("%s%s%s%s%s%s", + kVendorModelSerialPrefix, + vendor.c_str(), kNonSpaceDelim, + model.c_str(), kNonSpaceDelim, + serial_short.c_str()); +} + +// Get the device information using udev library. +// On success, returns true and fill in |unique_id|, |name|, and |removable|. +bool GetDeviceInfo(const FilePath& device_path, std::string* unique_id, + string16* name, bool* removable) { + DCHECK(!device_path.empty()); + + ScopedUdevObject udev_obj(udev_new()); + if (!udev_obj.get()) + return false; + + struct stat device_stat; + if (stat(device_path.value().c_str(), &device_stat) < 0) + return false; + + char device_type; + if (S_ISCHR(device_stat.st_mode)) + device_type = 'c'; + else if (S_ISBLK(device_stat.st_mode)) + device_type = 'b'; + else + return false; // Not a supported type. + + ScopedUdevDeviceObject device( + udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev)); + if (!device.get()) + return false; + + // Construct a device name using label or manufacturer (vendor and model) + // details. + if (name) { + std::string device_label = GetUdevDevicePropertyValue(device, kLabel); + if (device_label.empty()) + device_label = udev_device_get_property_value(device, kSerial); + if (device_label.empty()) { + // Format: VendorInfo ModelInfo + // E.g.: KnCompany Model2010 + std::string vendor_name = GetUdevDevicePropertyValue(device, kVendor); + std::string model_name = GetUdevDevicePropertyValue(device, kModel); + if (vendor_name.empty() && model_name.empty()) + return false; + + if (vendor_name.empty()) + device_label = model_name; + else if (model_name.empty()) + device_label = vendor_name; + else + device_label = vendor_name + kSpaceDelim + model_name; + } + + if (IsStringUTF8(device_label)) + *name = UTF8ToUTF16(device_label); + } + + if (unique_id) { + *unique_id = MakeDeviceUniqueId(device); + if (unique_id->empty()) + return false; + } + + if (removable) { + const char* value = udev_device_get_sysattr_value(device, + kRemovableSysAttr); + if (!value) { + // |parent_device| is owned by |device| and does not need to be cleaned + // up. + struct udev_device* parent_device = + udev_device_get_parent_with_subsystem_devtype(device, + kBlockSubsystemKey, + kDiskDeviceTypeKey); + value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr); + } + *removable = (value && atoi(value) == 1); + } + return true; +} + +} // namespace + +RemovableDeviceNotificationsLinux::MountPointInfo::MountPointInfo() { +} + +RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux( + const FilePath& path) + : initialized_(false), + mtab_path_(path), + get_device_info_func_(&GetDeviceInfo) { + DCHECK(!g_removable_device_notifications_linux); + g_removable_device_notifications_linux = this; +} + +RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux( + const FilePath& path, + GetDeviceInfoFunc get_device_info_func) + : initialized_(false), + mtab_path_(path), + get_device_info_func_(get_device_info_func) { + DCHECK(!g_removable_device_notifications_linux); + g_removable_device_notifications_linux = this; +} + +RemovableDeviceNotificationsLinux::~RemovableDeviceNotificationsLinux() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + DCHECK_EQ(this, g_removable_device_notifications_linux); + g_removable_device_notifications_linux = NULL; +} + +// static +RemovableDeviceNotificationsLinux* +RemovableDeviceNotificationsLinux::GetInstance() { + return g_removable_device_notifications_linux; +} + +void RemovableDeviceNotificationsLinux::Init() { + DCHECK(!mtab_path_.empty()); + + // Put |kKnownFileSystems| in std::set to get O(log N) access time. + for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) + known_file_systems_.insert(kKnownFileSystems[i]); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&RemovableDeviceNotificationsLinux::InitOnFileThread, this)); +} + +FilePath RemovableDeviceNotificationsLinux::GetDeviceMountPoint( + const std::string& device_id) const { + + MediaStorageUtil::Type type; + MediaStorageUtil::CrackDeviceId(device_id, &type, NULL); + if (type == MediaStorageUtil::MTP_OR_PTP) + return FilePath(); + DCHECK(type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM || + type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM || + type == MediaStorageUtil::FIXED_MASS_STORAGE); + + FilePath mount_device; + for (MountMap::const_iterator it = mount_info_map_.begin(); + it != mount_info_map_.end(); + ++it) { + if (it->second.device_id == device_id) { + mount_device = it->second.mount_device; + break; + } + } + if (mount_device.empty()) + return mount_device; + + const ReferencedMountPoint& referenced_info = + mount_priority_map_.find(mount_device)->second; + for (ReferencedMountPoint::const_iterator it = referenced_info.begin(); + it != referenced_info.end(); + ++it) { + if (it->second) + return it->first; + } + // If none of them are default, just return the first. + return FilePath(referenced_info.begin()->first); +} + +std::string RemovableDeviceNotificationsLinux::GetDeviceIdForPath( + const FilePath& path, FilePath* mount_point) const { + if (!path.IsAbsolute()) + return std::string(); + + FilePath current = path; + while (!ContainsKey(mount_info_map_, current) && current != current.DirName()) + current = current.DirName(); + + MountMap::const_iterator mount_info = mount_info_map_.find(current); + if (mount_info == mount_info_map_.end()) + return std::string(); + + if (mount_point) + *mount_point = current; + + return mount_info->second.device_id; +} + +void RemovableDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path, + bool error) { + if (path != mtab_path_) { + // This cannot happen unless FilePathWatcher is buggy. Just ignore this + // notification and do nothing. + NOTREACHED(); + return; + } + if (error) { + LOG(ERROR) << "Error watching " << mtab_path_.value(); + return; + } + + UpdateMtab(); +} + +void RemovableDeviceNotificationsLinux::InitOnFileThread() { + DCHECK(!initialized_); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + initialized_ = true; + + // The callback passed to Watch() has to be unretained. Otherwise + // RemovableDeviceNotificationsLinux will live longer than expected, and + // FilePathWatcher will get in trouble at shutdown time. + bool ret = file_watcher_.Watch( + mtab_path_, + base::Bind(&RemovableDeviceNotificationsLinux::OnFilePathChanged, + base::Unretained(this))); + if (!ret) { + LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed"; + return; + } + + UpdateMtab(); +} + +void RemovableDeviceNotificationsLinux::UpdateMtab() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + MountPointDeviceMap new_mtab; + ReadMtab(mtab_path_, known_file_systems_, &new_mtab); + + // Check existing mtab entries for unaccounted mount points. + // These mount points must have been removed in the new mtab. + std::list<FilePath> mount_points_to_erase; + std::list<FilePath> multiple_mounted_devices_needing_reattachment; + for (MountMap::const_iterator old_iter = mount_info_map_.begin(); + old_iter != mount_info_map_.end(); ++old_iter) { + const FilePath& mount_point = old_iter->first; + const FilePath& mount_device = old_iter->second.mount_device; + MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point); + // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at + // |mount_point|. + if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) { + MountPriorityMap::iterator priority = + mount_priority_map_.find(mount_device); + DCHECK(priority != mount_priority_map_.end()); + ReferencedMountPoint::const_iterator has_priority = + priority->second.find(mount_point); + if (old_iter->second.has_dcim) { + DCHECK(has_priority != priority->second.end()); + if (has_priority->second) + RemoveMediaMount(old_iter->second.device_id); + if (priority->second.size() > 1) + multiple_mounted_devices_needing_reattachment.push_back(mount_device); + } + priority->second.erase(mount_point); + if (priority->second.empty()) + mount_priority_map_.erase(mount_device); + mount_points_to_erase.push_back(mount_point); + } + } + + // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above + // using the iterator is slightly more efficient, but more tricky, since + // calling std::map::erase() on an iterator invalidates it. + for (std::list<FilePath>::const_iterator it = mount_points_to_erase.begin(); + it != mount_points_to_erase.end(); + ++it) { + mount_info_map_.erase(*it); + } + + // For any multiply mounted device where the mount that we had notified + // got detached, send a notification of attachment for one of the other + // mount points. + for (std::list<FilePath>::const_iterator it = + multiple_mounted_devices_needing_reattachment.begin(); + it != multiple_mounted_devices_needing_reattachment.end(); + ++it) { + ReferencedMountPoint::iterator first_mount_point_info = + mount_priority_map_.find(*it)->second.begin(); + const FilePath& mount_point = first_mount_point_info->first; + first_mount_point_info->second = true; + + const MountPointInfo& mount_info = + mount_info_map_.find(mount_point)->second; + DCHECK(mount_info.has_dcim); + base::SystemMonitor::Get()->ProcessRemovableStorageAttached( + mount_info.device_id, mount_info.device_name, mount_point.value()); + } + + // Check new mtab entries against existing ones. + for (MountPointDeviceMap::iterator new_iter = new_mtab.begin(); + new_iter != new_mtab.end(); ++new_iter) { + const FilePath& mount_point = new_iter->first; + const FilePath& mount_device = new_iter->second; + MountMap::iterator old_iter = mount_info_map_.find(mount_point); + if (old_iter == mount_info_map_.end() || + old_iter->second.mount_device != mount_device) { + // New mount point found or an existing mount point found with a new + // device. + AddNewMount(mount_device, mount_point); + } + } +} + +void RemovableDeviceNotificationsLinux::AddNewMount( + const FilePath& mount_device, const FilePath& mount_point) { + MountPriorityMap::iterator priority = + mount_priority_map_.find(mount_device); + if (priority != mount_priority_map_.end()) { + const FilePath& other_mount_point = priority->second.begin()->first; + priority->second[mount_point] = false; + mount_info_map_[mount_point] = + mount_info_map_.find(other_mount_point)->second; + return; + } + + std::string unique_id; + string16 name; + bool removable; + bool result = get_device_info_func_(mount_device, &unique_id, &name, + &removable); + + // Keep track of GetDeviceInfo result, to see how often we fail to get device + // details. + UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available", + result); + if (!result) + return; + + // Keep track of device uuid, to see how often we receive empty values. + UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available", + !unique_id.empty()); + UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available", + !name.empty()); + + if (unique_id.empty() || name.empty()) + return; + + bool has_dcim = IsMediaDevice(mount_point.value()); + MediaStorageUtil::Type type; + if (removable) { + if (has_dcim) { + type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM; + } else { + type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM; + } + } else { + type = MediaStorageUtil::FIXED_MASS_STORAGE; + } + std::string device_id = MediaStorageUtil::MakeDeviceId(type, unique_id); + + MountPointInfo mount_point_info; + mount_point_info.mount_device = mount_device; + mount_point_info.device_id = device_id; + mount_point_info.device_name = name; + mount_point_info.has_dcim = has_dcim; + + mount_info_map_[mount_point] = mount_point_info; + mount_priority_map_[mount_device][mount_point] = has_dcim; + + if (mount_point_info.has_dcim) { + SystemMonitor::Get()->ProcessRemovableStorageAttached(device_id, name, + mount_point.value()); + } +} + +void RemovableDeviceNotificationsLinux::RemoveMediaMount( + const std::string& device_id) { + SystemMonitor::Get()->ProcessRemovableStorageDetached(device_id); +} + +} // namespace chrome diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux.h b/chrome/browser/media_gallery/removable_device_notifications_linux.h new file mode 100644 index 0000000..a2419a2 --- /dev/null +++ b/chrome/browser/media_gallery/removable_device_notifications_linux.h @@ -0,0 +1,146 @@ +// Copyright (c) 2012 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. + +// RemovableDeviceNotificationsLinux listens for mount point changes, notifies +// the SystemMonitor about the addition and deletion of media devices, and +// answers queries about mounted devices. + +#ifndef CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_ +#define CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_ + +#if defined(OS_CHROMEOS) +#error "Use the ChromeOS-specific implementation instead." +#endif + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/ref_counted.h" +#include "content/public/browser/browser_thread.h" + +class FilePath; + +// Gets the media device information given a |device_path|. On success, +// returns true and fills in |unique_id|, |name|, and |removable|. +typedef bool (*GetDeviceInfoFunc)(const FilePath& device_path, + std::string* unique_id, string16* name, + bool* removable); + +namespace chrome { + +class RemovableDeviceNotificationsLinux + : public base::RefCountedThreadSafe<RemovableDeviceNotificationsLinux, + content::BrowserThread::DeleteOnFileThread> { + public: + // Should only be called by browser start up code. Use GetInstance() instead. + explicit RemovableDeviceNotificationsLinux(const FilePath& path); + + static RemovableDeviceNotificationsLinux* GetInstance(); + + // Must be called for RemovableDeviceNotificationsLinux to work. + void Init(); + + // Use |device_id| to find and return where the device is mounted. + FilePath GetDeviceMountPoint(const std::string& device_id) const; + + // Determines which device |path| is located on and returns the unique id + // for that device, otherwise returns an empty string. If |mount_point| is + // not NULL, set it to the mount point of the device. + std::string GetDeviceIdForPath(const FilePath& path, + FilePath* mount_point) const; + + protected: + // Only for use in unit tests. + RemovableDeviceNotificationsLinux(const FilePath& path, + GetDeviceInfoFunc getDeviceInfo); + + // Avoids code deleting the object while there are references to it. + // Aside from the base::RefCountedThreadSafe friend class, and derived + // classes, any attempts to call this dtor will result in a compile-time + // error. + virtual ~RemovableDeviceNotificationsLinux(); + + virtual void OnFilePathChanged(const FilePath& path, bool error); + + private: + friend class base::RefCountedThreadSafe<RemovableDeviceNotificationsLinux>; + friend class base::DeleteHelper<RemovableDeviceNotificationsLinux>; + friend struct content::BrowserThread::DeleteOnThread< + content::BrowserThread::FILE>; + + // Structure to save mounted device information such as device path and unique + // identifier. + struct MountPointInfo { + MountPointInfo(); + + FilePath mount_device; + std::string device_id; + string16 device_name; + bool has_dcim; + }; + + // Mapping of mount points to MountPointInfo. + typedef std::map<FilePath, MountPointInfo> MountMap; + + // (mount point, priority) + // For devices that are mounted to multiple mount points, this helps us track + // which one we've notified system monitor about. + typedef std::map<FilePath, bool> ReferencedMountPoint; + + // (mount device, map of known mount points) + // For each mount device, track the places it is mounted and which one (if + // any) we have notified system monitor about. + typedef std::map<FilePath, ReferencedMountPoint> MountPriorityMap; + + // Do initialization on the File Thread. + void InitOnFileThread(); + + // Parses mtab file and find all changes. + void UpdateMtab(); + + // Adds |mount_device| as mounted on |mount_point|. If the device is a new + // media device, SystemMonitor is notified. + void AddNewMount(const FilePath& mount_device, const FilePath& mount_point); + + // Removes media device with a given device id. + void RemoveMediaMount(const std::string& device_id); + + // Whether Init() has been called or not. + bool initialized_; + + // Mtab file that lists the mount points. + const FilePath mtab_path_; + + // Watcher for |mtab_path_|. + base::files::FilePathWatcher file_watcher_; + + // Set of known file systems that we care about. + std::set<std::string> known_file_systems_; + + // Function handler to get device information. This is useful to set a mock + // handler for unit testing. + GetDeviceInfoFunc get_device_info_func_; + + // Mapping of relevant mount points and their corresponding mount devices. + // Keep in mind on Linux, a device can be mounted at multiple mount points, + // and multiple devices can be mounted at a mount point. + MountMap mount_info_map_; + + // Because a device can be mounted to multiple places, we only want to + // notify about one of them. If (and only if) that one is unmounted, we need + // to notify about it's departure and notify about another one of it's mount + // points. + MountPriorityMap mount_priority_map_; + + DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationsLinux); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERY_REMOVABLE_DEVICE_NOTIFICATIONS_LINUX_H_ diff --git a/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc b/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc new file mode 100644 index 0000000..244560d --- /dev/null +++ b/chrome/browser/media_gallery/removable_device_notifications_linux_unittest.cc @@ -0,0 +1,670 @@ +// Copyright (c) 2012 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. + +// RemovableDeviceNotificationsLinux unit tests. + +#include "chrome/browser/media_gallery/removable_device_notifications_linux.h" + +#include <mntent.h> +#include <stdio.h> + +#include <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "base/system_monitor/system_monitor.h" +#include "base/test/mock_devices_changed_observer.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/media_gallery/media_storage_util.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chrome { + +namespace { + +using testing::_; + +const char kValidFS[] = "vfat"; +const char kInvalidFS[] = "invalidfs"; + +const char kInvalidPath[] = "invalid path does not exist"; + +const char kDevice1[] = "d1"; +const char kDevice2[] = "d2"; +const char kDevice3[] = "d3"; +const char kDevice4[] = "d4"; +const char kDevice5[] = "d5"; + +const char kInvalidDevice[] = "invalid_device"; + +const char kMountPointA[] = "mnt_a"; +const char kMountPointB[] = "mnt_b"; +const char kMountPointC[] = "mnt_c"; + +const char kDCIM[] = "DCIM"; + +struct TestDeviceData { + const char* device_path; + const char* unique_id; + const char* device_name; + MediaStorageUtil::Type type; +}; + +const TestDeviceData kTestDeviceData[] = { + {kDevice1, "UUID:FFF0-000F", "TEST_USB_MODEL_1", + MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM}, + {kDevice2, "VendorModelSerial:ComName:Model2010:89898989", "TEST_USB_MODEL_2", + MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM}, + {kDevice3, "VendorModelSerial:::WEM319X792", "TEST_USB_MODEL_3", + MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM}, + {kDevice4, "UUID:ABCD-1234", "TEST_USB_MODEL_4", + MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM}, + {kDevice5, "UUID:743A91FD2349", "TEST_USB_MODEL_5", + MediaStorageUtil::FIXED_MASS_STORAGE}, +}; + +bool GetDeviceInfo(const FilePath& device_path, std::string* id, + string16* name, bool* removable) { + for (size_t i = 0; i < arraysize(kTestDeviceData); i++) { + if (device_path.value() == kTestDeviceData[i].device_path) { + if (id) + *id = kTestDeviceData[i].unique_id; + if (name) + *name = ASCIIToUTF16(kTestDeviceData[i].device_name); + if (removable) { + MediaStorageUtil::Type type = kTestDeviceData[i].type; + *removable = + (type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM) || + (type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM); + } + return true; + } + } + return false; +} + +std::string GetDeviceId(const std::string& device) { + for (size_t i = 0; i < arraysize(kTestDeviceData); i++) { + if (device == kTestDeviceData[i].device_path) { + return MediaStorageUtil::MakeDeviceId(kTestDeviceData[i].type, + kTestDeviceData[i].unique_id); + } + } + if (device == kInvalidDevice) { + return MediaStorageUtil::MakeDeviceId(MediaStorageUtil::FIXED_MASS_STORAGE, + kInvalidDevice); + } + return std::string(); +} +string16 GetDeviceName(const std::string& device) { + for (size_t i = 0; i < arraysize(kTestDeviceData); i++) { + if (device == kTestDeviceData[i].device_path) + return ASCIIToUTF16(kTestDeviceData[i].device_name); + } + return string16(); +} + +class RemovableDeviceNotificationsLinuxTestWrapper + : public RemovableDeviceNotificationsLinux { + public: + RemovableDeviceNotificationsLinuxTestWrapper(const FilePath& path, + MessageLoop* message_loop) + : RemovableDeviceNotificationsLinux(path, &GetDeviceInfo), + message_loop_(message_loop) { + } + + private: + // Avoids code deleting the object while there are references to it. + // Aside from the base::RefCountedThreadSafe friend class, any attempts to + // call this dtor will result in a compile-time error. + ~RemovableDeviceNotificationsLinuxTestWrapper() {} + + virtual void OnFilePathChanged(const FilePath& path, bool error) OVERRIDE { + RemovableDeviceNotificationsLinux::OnFilePathChanged(path, error); + message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + } + + MessageLoop* message_loop_; + + DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationsLinuxTestWrapper); +}; + +class RemovableDeviceNotificationLinuxTest : public testing::Test { + public: + struct MtabTestData { + MtabTestData(const std::string& mount_device, + const std::string& mount_point, + const std::string& mount_type) + : mount_device(mount_device), + mount_point(mount_point), + mount_type(mount_type) { + } + + const std::string mount_device; + const std::string mount_point; + const std::string mount_type; + }; + + RemovableDeviceNotificationLinuxTest() + : message_loop_(MessageLoop::TYPE_IO), + file_thread_(content::BrowserThread::FILE, &message_loop_) { + } + virtual ~RemovableDeviceNotificationLinuxTest() {} + + protected: + virtual void SetUp() OVERRIDE { + mock_devices_changed_observer_.reset(new base::MockDevicesChangedObserver); + system_monitor_.AddDevicesChangedObserver( + mock_devices_changed_observer_.get()); + + // Create and set up a temp dir with files for the test. + ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); + FilePath test_dir = scoped_temp_dir_.path().AppendASCII("test_etc"); + ASSERT_TRUE(file_util::CreateDirectory(test_dir)); + mtab_file_ = test_dir.AppendASCII("test_mtab"); + MtabTestData initial_test_data[] = { + MtabTestData("dummydevice", "dummydir", kInvalidFS), + }; + WriteToMtab(initial_test_data, + arraysize(initial_test_data), + true /* overwrite */); + + // Initialize the test subject. + notifications_ = new RemovableDeviceNotificationsLinuxTestWrapper( + mtab_file_, &message_loop_); + notifications_->Init(); + message_loop_.RunAllPending(); + } + + virtual void TearDown() OVERRIDE { + message_loop_.RunAllPending(); + notifications_ = NULL; + system_monitor_.RemoveDevicesChangedObserver( + mock_devices_changed_observer_.get()); + } + + // Append mtab entries from the |data| array of size |data_size| to the mtab + // file, and run the message loop. + void AppendToMtabAndRunLoop(const MtabTestData* data, size_t data_size) { + WriteToMtab(data, data_size, false /* do not overwrite */); + message_loop_.Run(); + } + + // Overwrite the mtab file with mtab entries from the |data| array of size + // |data_size|, and run the message loop. + void OverwriteMtabAndRunLoop(const MtabTestData* data, size_t data_size) { + WriteToMtab(data, data_size, true /* overwrite */); + message_loop_.Run(); + } + + // Simplied version of OverwriteMtabAndRunLoop() that just deletes all the + // entries in the mtab file. + void WriteEmptyMtabAndRunLoop() { + OverwriteMtabAndRunLoop(NULL, // No data. + 0); // No data length. + } + + // Create a directory named |dir| relative to the test directory. + // It has a DCIM directory, so RemovableDeviceNotificationsLinux recognizes it + // as a media directory. + FilePath CreateMountPointWithDCIMDir(const std::string& dir) { + return CreateMountPoint(dir, true /* create DCIM dir */); + } + + // Create a directory named |dir| relative to the test directory. + // It does not have a DCIM directory, so RemovableDeviceNotificationsLinux + // does not recognizes it as a media directory. + FilePath CreateMountPointWithoutDCIMDir(const std::string& dir) { + return CreateMountPoint(dir, false /* do not create DCIM dir */); + } + + void RemoveDCIMDirFromMountPoint(const std::string& dir) { + FilePath dcim(scoped_temp_dir_.path().AppendASCII(dir).AppendASCII(kDCIM)); + file_util::Delete(dcim, false); + } + + base::MockDevicesChangedObserver& observer() { + return *mock_devices_changed_observer_; + } + + RemovableDeviceNotificationsLinux* notifier() { + return notifications_.get(); + } + + private: + // 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. + FilePath CreateMountPoint(const std::string& dir, bool with_dcim_dir) { + FilePath return_path(scoped_temp_dir_.path()); + return_path = return_path.AppendASCII(dir); + FilePath path(return_path); + if (with_dcim_dir) + path = path.AppendASCII(kDCIM); + if (!file_util::CreateDirectory(path)) + return FilePath(); + return return_path; + } + + // Write the test mtab data to |mtab_file_|. + // |data| is an array of mtab entries. + // |data_size| is the array size of |data|. + // |overwrite| specifies whether to overwrite |mtab_file_|. + void WriteToMtab(const MtabTestData* data, + size_t data_size, + bool overwrite) { + FILE* file = setmntent(mtab_file_.value().c_str(), overwrite ? "w" : "a"); + ASSERT_TRUE(file); + + // Due to the glibc *mntent() interface design, which is out of our + // control, the mtnent struct has several char* fields, even though + // addmntent() does not write to them in the calls below. To make the + // compiler happy while avoiding making additional copies of strings, + // we just const_cast() the strings' c_str()s. + // Assuming addmntent() does not write to the char* fields, this is safe. + // It is unlikely the platforms this test suite runs on will have an + // addmntent() implementation that does change the char* fields. If that + // was ever the case, the test suite will start crashing or failing. + mntent entry; + static const char kMountOpts[] = "rw"; + entry.mnt_opts = const_cast<char*>(kMountOpts); + entry.mnt_freq = 0; + entry.mnt_passno = 0; + for (size_t i = 0; i < data_size; ++i) { + entry.mnt_fsname = const_cast<char*>(data[i].mount_device.c_str()); + entry.mnt_dir = const_cast<char*>(data[i].mount_point.c_str()); + entry.mnt_type = const_cast<char*>(data[i].mount_type.c_str()); + ASSERT_EQ(0, addmntent(file, &entry)); + } + ASSERT_EQ(1, endmntent(file)); + } + + // The message loop and file thread to run tests on. + MessageLoop message_loop_; + content::TestBrowserThread file_thread_; + + // SystemMonitor and DevicesChangedObserver to hook together to test. + base::SystemMonitor system_monitor_; + scoped_ptr<base::MockDevicesChangedObserver> mock_devices_changed_observer_; + + // Temporary directory for created test data. + ScopedTempDir scoped_temp_dir_; + // Path to the test mtab file. + FilePath mtab_file_; + + scoped_refptr<RemovableDeviceNotificationsLinuxTestWrapper> notifications_; + + DISALLOW_COPY_AND_ASSIGN(RemovableDeviceNotificationLinuxTest); +}; + +// Simple test case where we attach and detach a media device. +TEST_F(RemovableDeviceNotificationLinuxTest, BasicAttachDetach) { + testing::Sequence mock_sequence; + FilePath test_path = CreateMountPointWithDCIMDir(kMountPointA); + ASSERT_FALSE(test_path.empty()); + MtabTestData test_data[] = { + MtabTestData(kDevice1, kInvalidPath, kValidFS), + MtabTestData(kDevice2, test_path.value(), kValidFS), + }; + // Only |kDevice2| should be attached, since |kDevice1| has a bad path. + EXPECT_CALL(observer(), OnRemovableStorageAttached(GetDeviceId(kDevice2), + GetDeviceName(kDevice2), + test_path.value())) + .InSequence(mock_sequence); + AppendToMtabAndRunLoop(test_data, arraysize(test_data)); + + // |kDevice2| should be detached here. + EXPECT_CALL(observer(), OnRemovableStorageDetached(GetDeviceId(kDevice2))) + .InSequence(mock_sequence); + WriteEmptyMtabAndRunLoop(); +} + +// Only mount points with DCIM directories are recognized. +TEST_F(RemovableDeviceNotificationLinuxTest, DCIM) { + testing::Sequence mock_sequence; + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + ASSERT_FALSE(test_path_a.empty()); + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + }; + // |kDevice1| should be attached as expected. + EXPECT_CALL(observer(), OnRemovableStorageAttached(GetDeviceId(kDevice1), + GetDeviceName(kDevice1), + test_path_a.value())) + .InSequence(mock_sequence); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + // This should do nothing, since |kMountPointB| does not have a DCIM dir. + FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); + ASSERT_FALSE(test_path_b.empty()); + MtabTestData test_data2[] = { + MtabTestData(kDevice2, test_path_b.value(), kValidFS), + }; + AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); + + // |kDevice1| should be detached as expected. + EXPECT_CALL(observer(), OnRemovableStorageDetached(GetDeviceId(kDevice1))) + .InSequence(mock_sequence); + WriteEmptyMtabAndRunLoop(); +} + +// More complicated test case with multiple devices on multiple mount points. +TEST_F(RemovableDeviceNotificationLinuxTest, SwapMountPoints) { + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + + // Attach two devices. + // kDevice1 -> kMountPointA + // kDevice2 -> kMountPointB + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice2, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + // Detach two devices from old mount points and attach the devices at new + // mount points. + // kDevice1 -> kMountPointB + // kDevice2 -> kMountPointA + MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + MtabTestData(kDevice2, test_path_a.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); + OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); + + // Detach all devices. + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); + WriteEmptyMtabAndRunLoop(); +} + +// More complicated test case with multiple devices on multiple mount points. +TEST_F(RemovableDeviceNotificationLinuxTest, MultiDevicesMultiMountPoints) { + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + + // Attach two devices. + // kDevice1 -> kMountPointA * + // kDevice2 -> kMountPointB * + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice2, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + // Attach |kDevice1| to |kMountPointB|. + // |kDevice2| is inaccessible, so it is detached. |kDevice1| has been + // attached at |kMountPointB|, but is still accessible from |kMountPointA|. + // kDevice1 -> kMountPointA * + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB + MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); + + // Detach |kDevice1| from |kMountPointA|, causing a detach and attach event. + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB * + MtabTestData test_data3[] = { + MtabTestData(kDevice2, test_path_b.value(), kValidFS), + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + OverwriteMtabAndRunLoop(test_data3, arraysize(test_data3)); + + // Attach |kDevice1| to |kMountPointA|. + // kDevice2 -> kMountPointB + // kDevice1 -> kMountPointB * + // kDevice1 -> kMountPointA + MtabTestData test_data4[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data4, arraysize(test_data4)); + + // Detach |kDevice1| from |kMountPointB|. + // kDevice1 -> kMountPointA * + // kDevice2 -> kMountPointB * + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(2); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + OverwriteMtabAndRunLoop(test_data1, arraysize(test_data1)); + + // Detach all devices. + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(2); + WriteEmptyMtabAndRunLoop(); +} + +TEST_F(RemovableDeviceNotificationLinuxTest, + MultipleMountPointsWithNonDCIMDevices) { + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + + // Attach to one first. + // kDevice1 -> kMountPointA * + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + // Attach |kDevice1| to |kMountPointB|. + // kDevice1 -> kMountPointA * + // kDevice1 -> kMountPointB + MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); + + // Attach |kDevice4| (a non-DCIM device) to |kMountPointA|. + // kDevice1 -> kMountPointA + // kDevice1 -> kMountPointB * + // kDevice4 -> kMountPointA + MtabTestData test_data3[] = { + MtabTestData(kDevice4, test_path_a.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + RemoveDCIMDirFromMountPoint(kMountPointA); + AppendToMtabAndRunLoop(test_data3, arraysize(test_data3)); + + // Detach |kDevice4|. + // kDevice1 -> kMountPointA + // kDevice1 -> kMountPointB * + MtabTestData test_data4[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); + + // Detach |kDevice1| from |kMountPointA|. + // kDevice1 -> kMountPointB * + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); + + // Detach all devices. + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + WriteEmptyMtabAndRunLoop(); +} + +TEST_F(RemovableDeviceNotificationLinuxTest, MountLookUp) { + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); + FilePath test_path_c = CreateMountPointWithoutDCIMDir(kMountPointC); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + ASSERT_FALSE(test_path_c.empty()); + + // Attach to one first. + // kDevice1 -> kMountPointA * + // kDevice4 -> kMountPointB + // kDevice5 -> kMountPointC + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice4, test_path_b.value(), kValidFS), + MtabTestData(kDevice5, test_path_c.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + EXPECT_EQ(test_path_a, + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1))); + EXPECT_EQ(test_path_b, + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice4))); + EXPECT_EQ(test_path_c, + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice5))); + EXPECT_EQ(FilePath(), + notifier()->GetDeviceMountPoint(GetDeviceId(kInvalidDevice))); + + // Make |kDevice1| mounted in multiple places. + // kDevice1 -> kMountPointA * + // kDevice1 -> kMountPointB + // kDevice1 -> kMountPointC + MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + MtabTestData(kDevice1, test_path_c.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + // Add DCIM dirs. + CreateMountPointWithDCIMDir(kMountPointB); + CreateMountPointWithDCIMDir(kMountPointC); + OverwriteMtabAndRunLoop(test_data2, arraysize(test_data2)); + + EXPECT_EQ(test_path_a, + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1))); + + // Unmount |kDevice1| from |kMountPointA|. + // kDevice1 -> kMountPointB * + // kDevice1 -> kMountPointC + // Either |kMountPointB| or |kMountPointC| is a valid new default, but + // it turns out that it will be B. + MtabTestData test_data3[] = { + MtabTestData(kDevice1, test_path_b.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + OverwriteMtabAndRunLoop(test_data3, arraysize(test_data3)); + + EXPECT_EQ(test_path_b, + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice1))); + + // Mount a non-removable device in multiple places. + // kDevice5 -> kMountPointA + // kDevice5 -> kMountPointB + // kDevice5 -> kMountPointC + MtabTestData test_data4[] = { + MtabTestData(kDevice5, test_path_a.value(), kValidFS), + MtabTestData(kDevice5, test_path_b.value(), kValidFS), + MtabTestData(kDevice5, test_path_c.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(1); + // Remove DCIM dirs. + RemoveDCIMDirFromMountPoint(kMountPointA); + RemoveDCIMDirFromMountPoint(kMountPointB); + RemoveDCIMDirFromMountPoint(kMountPointC); + OverwriteMtabAndRunLoop(test_data4, arraysize(test_data4)); + + // Any of the mount points would be valid. + EXPECT_EQ(test_path_a.value(), + notifier()->GetDeviceMountPoint(GetDeviceId(kDevice5)).value()); +} + +TEST_F(RemovableDeviceNotificationLinuxTest, DeviceLookUp) { + FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + FilePath test_path_b = CreateMountPointWithoutDCIMDir(kMountPointB); + FilePath test_path_c = CreateMountPointWithoutDCIMDir(kMountPointC); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + ASSERT_FALSE(test_path_c.empty()); + + // Attach to one first. + // kDevice1 -> kMountPointA * + // kDevice4 -> kMountPointB + // kDevice5 -> kMountPointC + MtabTestData test_data1[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice4, test_path_b.value(), kValidFS), + MtabTestData(kDevice5, test_path_c.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(1); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data1, arraysize(test_data1)); + + FilePath mount_point; + EXPECT_EQ(GetDeviceId(kDevice1), + notifier()->GetDeviceIdForPath(test_path_a, &mount_point)); + EXPECT_EQ(test_path_a.value(), mount_point.value()); + EXPECT_EQ(GetDeviceId(kDevice4), + notifier()->GetDeviceIdForPath(test_path_b, &mount_point)); + EXPECT_EQ(test_path_b.value(), mount_point.value()); + EXPECT_EQ(GetDeviceId(kDevice5), + notifier()->GetDeviceIdForPath(test_path_c, NULL)); + + // An invalid path. + EXPECT_EQ(std::string(), + notifier()->GetDeviceIdForPath(FilePath(kInvalidPath), NULL)); + + // Test filling in of the mount point. + EXPECT_EQ(GetDeviceId(kDevice1), notifier()->GetDeviceIdForPath( + test_path_a.Append("some/other/path"), &mount_point)); + EXPECT_EQ(test_path_a.value(), mount_point.value()); + + // One device attached at multiple points. + // kDevice1 -> kMountPointA * + // kDevice5 -> kMountPointB + // kDevice5 -> kMountPointC + MtabTestData test_data2[] = { + MtabTestData(kDevice1, test_path_a.value(), kValidFS), + MtabTestData(kDevice5, test_path_b.value(), kValidFS), + MtabTestData(kDevice5, test_path_c.value(), kValidFS), + }; + EXPECT_CALL(observer(), OnRemovableStorageAttached(_, _, _)).Times(0); + EXPECT_CALL(observer(), OnRemovableStorageDetached(_)).Times(0); + AppendToMtabAndRunLoop(test_data2, arraysize(test_data2)); + + EXPECT_EQ(GetDeviceId(kDevice1), + notifier()->GetDeviceIdForPath(test_path_a, NULL)); + EXPECT_EQ(GetDeviceId(kDevice5), + notifier()->GetDeviceIdForPath(test_path_b, NULL)); + EXPECT_EQ(GetDeviceId(kDevice5), + notifier()->GetDeviceIdForPath(test_path_c, NULL)); +} + +} // namespace + +} // namespace chrome diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 78f60fb..4e9bd3e 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -944,8 +944,6 @@ 'browser/media/media_stream_devices_menu_model.h', 'browser/media_gallery/media_device_notifications_chromeos.cc', 'browser/media_gallery/media_device_notifications_chromeos.h', - 'browser/media_gallery/media_device_notifications_linux.cc', - 'browser/media_gallery/media_device_notifications_linux.h', 'browser/media_gallery/media_device_notifications_utils.cc', 'browser/media_gallery/media_device_notifications_utils.h', 'browser/media_gallery/media_device_notifications_window_win.cc', @@ -966,6 +964,8 @@ 'browser/media_gallery/media_gallery_database_types.h', 'browser/media_gallery/media_storage_util.cc', 'browser/media_gallery/media_storage_util.h', + 'browser/media_gallery/removable_device_notifications_linux.cc', + 'browser/media_gallery/removable_device_notifications_linux.h', 'browser/memory_details.cc', 'browser/memory_details.h', 'browser/memory_details_android.cc', @@ -4123,8 +4123,8 @@ ['exclude', 'browser/icon_loader_linux.cc'], ['exclude', 'browser/icon_manager_linux.cc'], ['exclude', 'browser/idle_linux.cc'], - ['exclude', 'browser/media_gallery/media_device_notifications_linux.cc'], - ['exclude', 'browser/media_gallery/media_device_notifications_linux.h'], + ['exclude', 'browser/media_gallery/removable_device_notifications_linux.cc'], + ['exclude', 'browser/media_gallery/removable_device_notifications_linux.h'], ['exclude', 'browser/password_manager/native_backend_gnome_x.cc'], ['exclude', 'browser/password_manager/native_backend_gnome_x.h'], ['exclude', 'browser/password_manager/native_backend_kwallet_x.cc'], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index a4e229da..6d1c6c4 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1370,7 +1370,6 @@ 'browser/managed_mode_url_filter_unittest.cc', 'browser/media/media_internals_unittest.cc', 'browser/media_gallery/media_device_notifications_chromeos_unittest.cc', - 'browser/media_gallery/media_device_notifications_linux_unittest.cc', 'browser/media_gallery/media_device_notifications_utils_unittest.cc', 'browser/media_gallery/media_device_notifications_window_win_unittest.cc', 'browser/media_gallery/media_galleries_dialog_controller_mock.cc', @@ -1378,6 +1377,7 @@ 'browser/media_gallery/media_galleries_preferences_unittest.cc', 'browser/media_gallery/media_gallery_database_unittest.cc', 'browser/media_gallery/media_storage_util_unittest.cc', + 'browser/media_gallery/removable_device_notifications_linux_unittest.cc', 'browser/metrics/metrics_log_unittest.cc', 'browser/metrics/metrics_log_serializer_unittest.cc', 'browser/metrics/metrics_service_unittest.cc', @@ -2218,7 +2218,7 @@ }], ['chromeos==1', { 'sources/': [ - ['exclude', '^browser/media_gallery/media_device_notifications_linux_unittest.cc'], + ['exclude', '^browser/media_gallery/removable_device_notifications_linux_unittest.cc'], ['exclude', '^browser/password_manager/native_backend_gnome_x_unittest.cc'], ['exclude', '^browser/password_manager/native_backend_kwallet_x_unittest.cc'], ['exclude', '^browser/policy/user_cloud_policy_store_unittest.cc'], |