Index: chrome/browser/media_gallery/disk_mount_watcher_linux.cc |
diff --git a/chrome/browser/media_gallery/disk_mount_watcher_linux.cc b/chrome/browser/media_gallery/disk_mount_watcher_linux.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6a8f1a01de2c98d87ff8f5b31010eb8ab4334f13 |
--- /dev/null |
+++ b/chrome/browser/media_gallery/disk_mount_watcher_linux.cc |
@@ -0,0 +1,493 @@ |
+// 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. |
+ |
+// DiskMountWatcherLinux implementation. |
+ |
+#include "chrome/browser/media_gallery/disk_mount_watcher_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/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_storage_util.h" |
+ |
+namespace chrome { |
+ |
+using base::SystemMonitor; |
+using content::BrowserThread; |
+ |
+namespace { |
+ |
+static DiskMountWatcherLinux* g_disk_mount_watcher_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 kBus[] = "ID_BUS"; |
+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 kUsbBus[] = "usb"; |
+const char kVendor[] = "ID_VENDOR"; |
+const char kVendorID[] = "ID_VENDOR_ID"; |
+ |
+// Delimiter constants. |
+const char kNonSpaceDelim[] = ":"; |
+const char kSpaceDelim[] = " "; |
+ |
+// Unique id prefix constants. |
+const char kFSUniqueIdPrefix[] = "UUID:"; |
+const char kVendorModelSerialPrefix[] = "VendorModelSerial:"; |
+ |
+// (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(FilePath mtab_path, |
+ std::set<std::string> interesting_file_systems, |
+ MountPointDeviceMap* mtab) { |
+ FILE* fp = setmntent(mtab_path.value().c_str(), "r"); |
+ if (!fp) |
+ return; |
+ |
+ mtab->clear(); |
+ |
+ 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); |
+ } |
+}; |
+ |
+// ScopedGenericObj functor for UdevDeviceObjectRelease(). |
+class ScopedReleaseUdevDeviceObject { |
+ public: |
+ void operator()(struct udev_device* device) const { |
+ udev_device_unref(device); |
+ } |
+}; |
+ |
+// Get the device information using udev library. |
+// On success, returns true and fill in |id| and |name|. |
+bool GetDeviceInfo(const FilePath& device_path, std::string* unique_id, |
+ string16* name, bool* usb) { |
+ DCHECK(!device_path.empty()); |
+ |
+ ScopedGenericObj<struct udev*, ScopedReleaseUdevObject> 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. |
+ |
+ ScopedGenericObj<struct udev_device*, ScopedReleaseUdevDeviceObject> |
+ 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; |
+ const char* device_name = NULL; |
+ if ((device_name = udev_device_get_property_value(device, kLabel)) || |
+ (device_name = udev_device_get_property_value(device, kSerial))) { |
+ device_label = device_name; |
+ } else { |
+ // Format: VendorInfo ModelInfo |
+ // E.g.: KnCompany Model2010 |
+ const char* vendor_name = NULL; |
+ if ((vendor_name = udev_device_get_property_value(device, kVendor))) |
+ device_label = vendor_name; |
+ |
+ const char* model_name = NULL; |
+ if ((model_name = udev_device_get_property_value(device, kModel))) { |
+ if (!device_label.empty()) |
+ device_label += kSpaceDelim; |
+ device_label += model_name; |
+ } |
+ } |
+ |
+ if (IsStringUTF8(device_label)) |
+ *name = UTF8ToUTF16(device_label); |
+ } |
+ |
+ if (unique_id) { |
+ const char* uuid = NULL; |
+ if ((uuid = udev_device_get_property_value(device, kFsUUID))) { |
+ *unique_id = std::string(kFSUniqueIdPrefix) + uuid; |
+ } else { |
+ // 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 |
+ const char* vendor = udev_device_get_property_value(device, kVendorID); |
+ const char* model = udev_device_get_property_value(device, kModelID); |
+ const char* serial_short = udev_device_get_property_value(device, |
+ kSerialShort); |
+ if (!vendor && !model && !serial_short) |
+ return false; |
+ |
+ *unique_id = std::string(kVendorModelSerialPrefix) + |
+ (vendor ? vendor : "") + kNonSpaceDelim + |
+ (model ? model : "") + kNonSpaceDelim + |
+ (serial_short ? serial_short : ""); |
+ } |
+ /* |
+ MediaStorageUtil::Type type; |
+ if (std::string(kUsbBus) == bus) { |
+ if (has_dcim) { |
+ type = MediaStorageUtil::USB_MASS_STORAGE_WITH_DCIM; |
+ } else { |
+ type = MediaStorageUtil::USB_MASS_STORAGE_NO_DCIM; |
+ } |
+ } else { |
+ type = MediaStorageUtil::OTHER_MASS_STORAGE; |
+ } |
+ */ |
+ } |
+ |
+ if (usb) { |
Lei Zhang
2012/08/25 01:22:19
FWIW, you can do:
const char* removable = udev_de
vandebo (ex-Chrome)
2012/08/27 19:42:38
Done.
|
+ const char* bus = udev_device_get_property_value(device, kBus); |
+ *usb = (std::string(kUsbBus) == bus); |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+DiskMountWatcherLinux::DiskMountWatcherLinux(const FilePath& path) |
+ : initialized_(false), |
+ mtab_path_(path), |
+ get_device_info_func_(&GetDeviceInfo) { |
+ DCHECK(g_disk_mount_watcher_linux == NULL); |
+ g_disk_mount_watcher_linux = this; |
+} |
+ |
+DiskMountWatcherLinux::DiskMountWatcherLinux(const FilePath& path, |
+ GetDeviceInfoFunc get_device_info_func) |
+ : initialized_(false), |
+ mtab_path_(path), |
+ get_device_info_func_(get_device_info_func) { |
+ DCHECK(g_disk_mount_watcher_linux == NULL); |
+ g_disk_mount_watcher_linux = this; |
+} |
+ |
+DiskMountWatcherLinux::~DiskMountWatcherLinux() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ DCHECK(g_disk_mount_watcher_linux != NULL); |
+ g_disk_mount_watcher_linux = NULL; |
+} |
+ |
+// static |
+DiskMountWatcherLinux* DiskMountWatcherLinux::GetInstance() { |
+ return g_disk_mount_watcher_linux; |
+} |
+ |
+void DiskMountWatcherLinux::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(&DiskMountWatcherLinux::InitOnFileThread, this)); |
+} |
+ |
+FilePath DiskMountWatcherLinux::GetDeviceMountPoint( |
+ const std::string& device_id) const { |
+ |
+ MediaStorageUtil::Type type; |
+ MediaStorageUtil::CrackDeviceId(device_id, &type, NULL); |
+ if (type == MediaStorageUtil::USB_MTP) |
+ return FilePath(); |
+ |
+ FilePath mount_device = FilePath(); |
+ 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()) { |
+ NOTREACHED(); |
+ 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( |
+ mount_priority_map_.find(mount_device)->second.begin()->first); |
+} |
+ |
+std::string DiskMountWatcherLinux::GetDeviceIdForPath( |
+ const FilePath& path, FilePath* mount_point) const { |
+ if (!path.IsAbsolute()) |
+ return std::string(); |
+ |
+ FilePath current = path; |
+ for (FilePath current = path; |
+ mount_info_map_.find(current) == mount_info_map_.end() && |
+ current != current.DirName(); |
+ current = current.DirName()) {} |
+ |
+ if (mount_info_map_.find(current) == mount_info_map_.end()) |
+ return std::string(); |
+ |
+ if (mount_point) |
+ *mount_point = current; |
+ |
+ return mount_info_map_.find(current)->second.device_id; |
+} |
+ |
+void DiskMountWatcherLinux::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 DiskMountWatcherLinux::InitOnFileThread() { |
+ DCHECK(!initialized_); |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ initialized_ = true; |
+ |
+ // The callback passed to Watch() has to be unretained. Otherwise |
+ // DiskMountWatcherLinux will live longer than expected, and |
+ // FilePathWatcher will get in trouble at shutdown time. |
+ bool ret = file_watcher_.Watch( |
+ mtab_path_, |
+ base::Bind(&DiskMountWatcherLinux::OnFilePathChanged, |
+ base::Unretained(this))); |
+ if (!ret) { |
+ LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed"; |
+ return; |
+ } |
+ |
+ UpdateMtab(); |
+} |
+ |
+void DiskMountWatcherLinux::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)) { |
+ if (old_iter->second.has_dcim) { |
+ 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); |
+ DCHECK(has_priority != priority->second.end()); |
+ if (has_priority->second) |
+ RemoveMediaMount(old_iter->second.device_id); |
+ priority->second.erase(mount_point); |
+ if (mount_priority_map_.find(mount_device)->second.empty()) { |
+ mount_priority_map_.erase(mount_device); |
+ } else { |
+ multiple_mounted_devices_needing_reattachment.push_back(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) { |
+ const FilePath& mount_point = mount_priority_map_[*it].begin()->first; |
+ mount_priority_map_[*it].begin()->second = true; |
+ DCHECK(mount_info_map_[mount_point].has_dcim); |
+ base::SystemMonitor::Get()->ProcessMediaDeviceAttached( |
+ mount_info_map_[mount_point].device_id, |
+ mount_info_map_[mount_point].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 DiskMountWatcherLinux::AddNewMount(const FilePath& mount_device, |
+ const FilePath& mount_point) { |
+ if (mount_priority_map_.find(mount_device) != mount_priority_map_.end()) { |
+ const FilePath& other_mount_point = |
+ mount_priority_map_[mount_device].begin()->first; |
+ mount_priority_map_[mount_device][mount_point] = false; |
+ mount_info_map_[mount_point] = mount_info_map_[other_mount_point]; |
+ return; |
+ } |
+ |
+ std::string unique_id; |
+ string16 name; |
+ bool usb; |
+ bool result = (*get_device_info_func_)(mount_device, &unique_id, &name, &usb); |
+ |
+ // 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 (usb) { |
+ if (has_dcim) { |
+ type = MediaStorageUtil::USB_MASS_STORAGE_WITH_DCIM; |
+ } else { |
+ type = MediaStorageUtil::USB_MASS_STORAGE_NO_DCIM; |
+ } |
+ } else { |
+ type = MediaStorageUtil::OTHER_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] = true; |
+ |
+ if (mount_point_info.has_dcim) { |
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); |
+ system_monitor->ProcessMediaDeviceAttached(device_id, name, |
+ mount_point.value()); |
+ } |
+} |
+ |
+void DiskMountWatcherLinux::RemoveMediaMount(const std::string& device_id) { |
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); |
+ system_monitor->ProcessMediaDeviceDetached(device_id); |
+} |
+ |
+} // namespace chrome |