Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1126)

Unified Diff: chrome/browser/media_gallery/disk_mount_watcher_linux.cc

Issue 10882039: Make the Linux System Monitor implementation track all devices (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698