Revert 168224 - Update V8 to version 3.15.4.
[chromium-blink-merge.git] / chrome / browser / system_monitor / removable_device_notifications_linux.cc
blob9e04d3a673fc076870b9871c593a3ae37ef5e817
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // RemovableDeviceNotificationsLinux implementation.
7 #include "chrome/browser/system_monitor/removable_device_notifications_linux.h"
9 #include <libudev.h>
10 #include <mntent.h>
11 #include <stdio.h>
13 #include <list>
15 #include "base/basictypes.h"
16 #include "base/bind.h"
17 #include "base/file_path.h"
18 #include "base/memory/scoped_generic_obj.h"
19 #include "base/metrics/histogram.h"
20 #include "base/stl_util.h"
21 #include "base/string_number_conversions.h"
22 #include "base/string_util.h"
23 #include "base/system_monitor/system_monitor.h"
24 #include "base/utf_string_conversions.h"
25 #include "chrome/browser/system_monitor/media_device_notifications_utils.h"
26 #include "chrome/browser/system_monitor/removable_device_constants.h"
27 #include "chrome/browser/system_monitor/media_storage_util.h"
29 namespace chrome {
31 using content::BrowserThread;
33 namespace {
35 static RemovableDeviceNotificationsLinux*
36 g_removable_device_notifications_linux = NULL;
38 // List of file systems we care about.
39 const char* const kKnownFileSystems[] = {
40 "ext2",
41 "ext3",
42 "ext4",
43 "fat",
44 "hfsplus",
45 "iso9660",
46 "msdos",
47 "ntfs",
48 "udf",
49 "vfat",
52 // udev device property constants.
53 const char kBlockSubsystemKey[] = "block";
54 const char kDiskDeviceTypeKey[] = "disk";
55 const char kFsUUID[] = "ID_FS_UUID";
56 const char kLabel[] = "ID_FS_LABEL";
57 const char kModel[] = "ID_MODEL";
58 const char kModelID[] = "ID_MODEL_ID";
59 const char kRemovableSysAttr[] = "removable";
60 const char kSerialShort[] = "ID_SERIAL_SHORT";
61 const char kSizeSysAttr[] = "size";
62 const char kVendor[] = "ID_VENDOR";
63 const char kVendorID[] = "ID_VENDOR_ID";
65 // (mount point, mount device)
66 // A mapping from mount point to mount device, as extracted from the mtab
67 // file.
68 typedef std::map<FilePath, FilePath> MountPointDeviceMap;
70 // Reads mtab file entries into |mtab|.
71 void ReadMtab(const FilePath& mtab_path,
72 const std::set<std::string>& interesting_file_systems,
73 MountPointDeviceMap* mtab) {
74 mtab->clear();
76 FILE* fp = setmntent(mtab_path.value().c_str(), "r");
77 if (!fp)
78 return;
80 mntent entry;
81 char buf[512];
83 // We return the same device mounted to multiple locations, but hide
84 // devices that have been mounted over.
85 while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
86 // We only care about real file systems.
87 if (!ContainsKey(interesting_file_systems, entry.mnt_type))
88 continue;
90 (*mtab)[FilePath(entry.mnt_dir)] = FilePath(entry.mnt_fsname);
92 endmntent(fp);
95 // ScopedGenericObj functor for UdevObjectRelease().
96 class ScopedReleaseUdevObject {
97 public:
98 void operator()(struct udev* udev) const {
99 udev_unref(udev);
102 typedef ScopedGenericObj<struct udev*,
103 ScopedReleaseUdevObject> ScopedUdevObject;
105 // ScopedGenericObj functor for UdevDeviceObjectRelease().
106 class ScopedReleaseUdevDeviceObject {
107 public:
108 void operator()(struct udev_device* device) const {
109 udev_device_unref(device);
112 typedef ScopedGenericObj<struct udev_device*,
113 ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject;
115 // Wrapper function for udev_device_get_property_value() that also checks for
116 // valid but empty values.
117 std::string GetUdevDevicePropertyValue(struct udev_device* udev_device,
118 const char* key) {
119 const char* value = udev_device_get_property_value(udev_device, key);
120 if (!value)
121 return std::string();
122 return (strlen(value) > 0) ? value : std::string();
125 // Construct a device id using label or manufacturer (vendor and model) details.
126 std::string MakeDeviceUniqueId(struct udev_device* device) {
127 std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
128 if (!uuid.empty())
129 return kFSUniqueIdPrefix + uuid;
131 // If one of the vendor, model, serial information is missing, its value
132 // in the string is empty.
133 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
134 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
135 std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
136 std::string model = GetUdevDevicePropertyValue(device, kModelID);
137 std::string serial_short = GetUdevDevicePropertyValue(device,
138 kSerialShort);
139 if (vendor.empty() && model.empty() && serial_short.empty())
140 return std::string();
142 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
145 // Records GetDeviceInfo result, to see how often we fail to get device details.
146 void RecordGetDeviceInfoResult(bool result) {
147 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess", result);
150 // Returns the storage partition size of the device specified by |device_path|.
151 // If the requested information is unavailable, returns 0.
152 uint64 GetDeviceStorageSize(const FilePath& device_path,
153 struct udev_device* device) {
154 // sysfs provides the device size in units of 512-byte blocks.
155 const std::string partition_size = udev_device_get_sysattr_value(
156 device, kSizeSysAttr);
158 // Keep track of device size, to see how often this information is
159 // unavailable.
160 UMA_HISTOGRAM_BOOLEAN(
161 "RemovableDeviceNotificationsLinux.device_partition_size_available",
162 !partition_size.empty());
164 uint64 total_size_in_bytes = 0;
165 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
166 return 0;
167 return (total_size_in_bytes <= kuint64max / 512) ?
168 total_size_in_bytes * 512 : 0;
171 // Constructs the device name from the device properties. If the device details
172 // are unavailable, returns an empty string.
173 string16 GetDeviceName(struct udev_device* device) {
174 std::string device_label = GetUdevDevicePropertyValue(device, kLabel);
175 if (!device_label.empty() && IsStringUTF8(device_label))
176 return UTF8ToUTF16(device_label);
178 device_label = GetUdevDevicePropertyValue(device, kFsUUID);
179 // Keep track of device uuid, to see how often we receive empty uuid values.
180 UMA_HISTOGRAM_BOOLEAN(
181 "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
182 !device_label.empty());
184 const string16 name = GetFullProductName(
185 GetUdevDevicePropertyValue(device, kVendor),
186 GetUdevDevicePropertyValue(device, kModel));
188 const string16 device_label_utf16 =
189 (!device_label.empty() && IsStringUTF8(device_label)) ?
190 UTF8ToUTF16(device_label) : string16();
191 if (!name.empty() && !device_label_utf16.empty())
192 return device_label_utf16 + ASCIIToUTF16(" ") + name;
193 return name.empty() ? device_label_utf16 : name;
196 // Get the device information using udev library.
197 // On success, returns true and fill in |unique_id|, |name|, |removable| and
198 // |partition_size_in_bytes|.
199 void GetDeviceInfo(const FilePath& device_path,
200 std::string* unique_id,
201 string16* name,
202 bool* removable,
203 uint64* partition_size_in_bytes) {
204 DCHECK(!device_path.empty());
205 ScopedUdevObject udev_obj(udev_new());
206 if (!udev_obj.get()) {
207 RecordGetDeviceInfoResult(false);
208 return;
211 struct stat device_stat;
212 if (stat(device_path.value().c_str(), &device_stat) < 0) {
213 RecordGetDeviceInfoResult(false);
214 return;
217 char device_type;
218 if (S_ISCHR(device_stat.st_mode)) {
219 device_type = 'c';
220 } else if (S_ISBLK(device_stat.st_mode)) {
221 device_type = 'b';
222 } else {
223 RecordGetDeviceInfoResult(false);
224 return; // Not a supported type.
227 ScopedUdevDeviceObject device(
228 udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev));
229 if (!device.get()) {
230 RecordGetDeviceInfoResult(false);
231 return;
234 if (name)
235 *name = GetDeviceName(device);
237 if (unique_id)
238 *unique_id = MakeDeviceUniqueId(device);
240 if (removable) {
241 const char* value = udev_device_get_sysattr_value(device,
242 kRemovableSysAttr);
243 if (!value) {
244 // |parent_device| is owned by |device| and does not need to be cleaned
245 // up.
246 struct udev_device* parent_device =
247 udev_device_get_parent_with_subsystem_devtype(device,
248 kBlockSubsystemKey,
249 kDiskDeviceTypeKey);
250 value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
252 *removable = (value && atoi(value) == 1);
255 if (partition_size_in_bytes)
256 *partition_size_in_bytes = GetDeviceStorageSize(device_path, device);
257 RecordGetDeviceInfoResult(true);
260 } // namespace
262 RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
263 const FilePath& path)
264 : initialized_(false),
265 mtab_path_(path),
266 get_device_info_func_(&GetDeviceInfo) {
267 DCHECK(!g_removable_device_notifications_linux);
268 g_removable_device_notifications_linux = this;
271 RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
272 const FilePath& path,
273 GetDeviceInfoFunc get_device_info_func)
274 : initialized_(false),
275 mtab_path_(path),
276 get_device_info_func_(get_device_info_func) {
277 DCHECK(!g_removable_device_notifications_linux);
278 g_removable_device_notifications_linux = this;
281 RemovableDeviceNotificationsLinux::~RemovableDeviceNotificationsLinux() {
282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
283 DCHECK_EQ(this, g_removable_device_notifications_linux);
284 g_removable_device_notifications_linux = NULL;
287 // static
288 RemovableDeviceNotificationsLinux*
289 RemovableDeviceNotificationsLinux::GetInstance() {
290 DCHECK(g_removable_device_notifications_linux != NULL);
291 return g_removable_device_notifications_linux;
294 void RemovableDeviceNotificationsLinux::Init() {
295 DCHECK(!mtab_path_.empty());
297 // Put |kKnownFileSystems| in std::set to get O(log N) access time.
298 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i)
299 known_file_systems_.insert(kKnownFileSystems[i]);
301 BrowserThread::PostTask(
302 BrowserThread::FILE, FROM_HERE,
303 base::Bind(&RemovableDeviceNotificationsLinux::InitOnFileThread, this));
306 bool RemovableDeviceNotificationsLinux::GetDeviceInfoForPath(
307 const FilePath& path,
308 base::SystemMonitor::RemovableStorageInfo* device_info) const {
309 if (!path.IsAbsolute())
310 return false;
312 FilePath current = path;
313 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
314 current = current.DirName();
316 MountMap::const_iterator mount_info = mount_info_map_.find(current);
317 if (mount_info == mount_info_map_.end())
318 return false;
320 if (device_info) {
321 device_info->device_id = mount_info->second.device_id;
322 device_info->name = mount_info->second.device_name;
323 device_info->location = current.value();
325 return true;
328 uint64 RemovableDeviceNotificationsLinux::GetStorageSize(
329 const std::string& location) const {
330 MountMap::const_iterator mount_info = mount_info_map_.find(
331 FilePath(location));
332 return (mount_info != mount_info_map_.end()) ?
333 mount_info->second.partition_size_in_bytes : 0;
336 void RemovableDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path,
337 bool error) {
338 if (path != mtab_path_) {
339 // This cannot happen unless FilePathWatcher is buggy. Just ignore this
340 // notification and do nothing.
341 NOTREACHED();
342 return;
344 if (error) {
345 LOG(ERROR) << "Error watching " << mtab_path_.value();
346 return;
349 UpdateMtab();
352 RemovableDeviceNotificationsLinux::MountPointInfo::MountPointInfo()
353 : partition_size_in_bytes(0) {
356 void RemovableDeviceNotificationsLinux::InitOnFileThread() {
357 DCHECK(!initialized_);
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
359 initialized_ = true;
361 // The callback passed to Watch() has to be unretained. Otherwise
362 // RemovableDeviceNotificationsLinux will live longer than expected, and
363 // FilePathWatcher will get in trouble at shutdown time.
364 bool ret = file_watcher_.Watch(
365 mtab_path_,
366 base::Bind(&RemovableDeviceNotificationsLinux::OnFilePathChanged,
367 base::Unretained(this)));
368 if (!ret) {
369 LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
370 return;
373 UpdateMtab();
376 void RemovableDeviceNotificationsLinux::UpdateMtab() {
377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
379 MountPointDeviceMap new_mtab;
380 ReadMtab(mtab_path_, known_file_systems_, &new_mtab);
382 // Check existing mtab entries for unaccounted mount points.
383 // These mount points must have been removed in the new mtab.
384 std::list<FilePath> mount_points_to_erase;
385 std::list<FilePath> multiple_mounted_devices_needing_reattachment;
386 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
387 old_iter != mount_info_map_.end(); ++old_iter) {
388 const FilePath& mount_point = old_iter->first;
389 const FilePath& mount_device = old_iter->second.mount_device;
390 MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point);
391 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
392 // |mount_point|.
393 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
394 MountPriorityMap::iterator priority =
395 mount_priority_map_.find(mount_device);
396 DCHECK(priority != mount_priority_map_.end());
397 ReferencedMountPoint::const_iterator has_priority =
398 priority->second.find(mount_point);
399 if (MediaStorageUtil::IsRemovableDevice(old_iter->second.device_id)) {
400 DCHECK(has_priority != priority->second.end());
401 if (has_priority->second) {
402 base::SystemMonitor::Get()->ProcessRemovableStorageDetached(
403 old_iter->second.device_id);
405 if (priority->second.size() > 1)
406 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
408 priority->second.erase(mount_point);
409 if (priority->second.empty())
410 mount_priority_map_.erase(mount_device);
411 mount_points_to_erase.push_back(mount_point);
415 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
416 // using the iterator is slightly more efficient, but more tricky, since
417 // calling std::map::erase() on an iterator invalidates it.
418 for (std::list<FilePath>::const_iterator it = mount_points_to_erase.begin();
419 it != mount_points_to_erase.end();
420 ++it) {
421 mount_info_map_.erase(*it);
424 // For any multiply mounted device where the mount that we had notified
425 // got detached, send a notification of attachment for one of the other
426 // mount points.
427 for (std::list<FilePath>::const_iterator it =
428 multiple_mounted_devices_needing_reattachment.begin();
429 it != multiple_mounted_devices_needing_reattachment.end();
430 ++it) {
431 ReferencedMountPoint::iterator first_mount_point_info =
432 mount_priority_map_.find(*it)->second.begin();
433 const FilePath& mount_point = first_mount_point_info->first;
434 first_mount_point_info->second = true;
436 const MountPointInfo& mount_info =
437 mount_info_map_.find(mount_point)->second;
438 DCHECK(MediaStorageUtil::IsRemovableDevice(mount_info.device_id));
439 base::SystemMonitor::Get()->ProcessRemovableStorageAttached(
440 mount_info.device_id, mount_info.device_name, mount_point.value());
443 // Check new mtab entries against existing ones.
444 for (MountPointDeviceMap::iterator new_iter = new_mtab.begin();
445 new_iter != new_mtab.end(); ++new_iter) {
446 const FilePath& mount_point = new_iter->first;
447 const FilePath& mount_device = new_iter->second;
448 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
449 if (old_iter == mount_info_map_.end() ||
450 old_iter->second.mount_device != mount_device) {
451 // New mount point found or an existing mount point found with a new
452 // device.
453 AddNewMount(mount_device, mount_point);
458 void RemovableDeviceNotificationsLinux::AddNewMount(
459 const FilePath& mount_device, const FilePath& mount_point) {
460 MountPriorityMap::iterator priority =
461 mount_priority_map_.find(mount_device);
462 if (priority != mount_priority_map_.end()) {
463 const FilePath& other_mount_point = priority->second.begin()->first;
464 priority->second[mount_point] = false;
465 mount_info_map_[mount_point] =
466 mount_info_map_.find(other_mount_point)->second;
467 return;
470 std::string unique_id;
471 string16 name;
472 bool removable;
473 uint64 partition_size_in_bytes;
474 get_device_info_func_(mount_device, &unique_id, &name, &removable,
475 &partition_size_in_bytes);
477 // Keep track of device info details to see how often we get invalid values.
478 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, name);
479 if (unique_id.empty() || name.empty())
480 return;
482 bool has_dcim = IsMediaDevice(mount_point.value());
483 MediaStorageUtil::Type type;
484 if (removable) {
485 if (has_dcim) {
486 type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM;
487 } else {
488 type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM;
490 } else {
491 type = MediaStorageUtil::FIXED_MASS_STORAGE;
493 std::string device_id = MediaStorageUtil::MakeDeviceId(type, unique_id);
495 MountPointInfo mount_point_info;
496 mount_point_info.mount_device = mount_device;
497 mount_point_info.device_id = device_id;
498 mount_point_info.device_name = name;
499 mount_point_info.partition_size_in_bytes = partition_size_in_bytes;
501 mount_info_map_[mount_point] = mount_point_info;
502 mount_priority_map_[mount_device][mount_point] = removable;
504 if (removable) {
505 base::SystemMonitor::Get()->ProcessRemovableStorageAttached(
506 device_id, GetDisplayNameForDevice(partition_size_in_bytes, name),
507 mount_point.value());
511 } // namespace chrome