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"
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"
31 using content::BrowserThread
;
35 static RemovableDeviceNotificationsLinux
*
36 g_removable_device_notifications_linux
= NULL
;
38 // List of file systems we care about.
39 const char* const kKnownFileSystems
[] = {
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
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
) {
76 FILE* fp
= setmntent(mtab_path
.value().c_str(), "r");
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
))
90 (*mtab
)[FilePath(entry
.mnt_dir
)] = FilePath(entry
.mnt_fsname
);
95 // ScopedGenericObj functor for UdevObjectRelease().
96 class ScopedReleaseUdevObject
{
98 void operator()(struct udev
* udev
) const {
102 typedef ScopedGenericObj
<struct udev
*,
103 ScopedReleaseUdevObject
> ScopedUdevObject
;
105 // ScopedGenericObj functor for UdevDeviceObjectRelease().
106 class ScopedReleaseUdevDeviceObject
{
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
,
119 const char* value
= udev_device_get_property_value(udev_device
, key
);
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
);
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
,
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
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
))
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
,
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);
211 struct stat device_stat
;
212 if (stat(device_path
.value().c_str(), &device_stat
) < 0) {
213 RecordGetDeviceInfoResult(false);
218 if (S_ISCHR(device_stat
.st_mode
)) {
220 } else if (S_ISBLK(device_stat
.st_mode
)) {
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
));
230 RecordGetDeviceInfoResult(false);
235 *name
= GetDeviceName(device
);
238 *unique_id
= MakeDeviceUniqueId(device
);
241 const char* value
= udev_device_get_sysattr_value(device
,
244 // |parent_device| is owned by |device| and does not need to be cleaned
246 struct udev_device
* parent_device
=
247 udev_device_get_parent_with_subsystem_devtype(device
,
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);
262 RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
263 const FilePath
& path
)
264 : initialized_(false),
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),
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
;
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())
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())
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();
328 uint64
RemovableDeviceNotificationsLinux::GetStorageSize(
329 const std::string
& location
) const {
330 MountMap::const_iterator mount_info
= mount_info_map_
.find(
332 return (mount_info
!= mount_info_map_
.end()) ?
333 mount_info
->second
.partition_size_in_bytes
: 0;
336 void RemovableDeviceNotificationsLinux::OnFilePathChanged(const FilePath
& path
,
338 if (path
!= mtab_path_
) {
339 // This cannot happen unless FilePathWatcher is buggy. Just ignore this
340 // notification and do nothing.
345 LOG(ERROR
) << "Error watching " << mtab_path_
.value();
352 RemovableDeviceNotificationsLinux::MountPointInfo::MountPointInfo()
353 : partition_size_in_bytes(0) {
356 void RemovableDeviceNotificationsLinux::InitOnFileThread() {
357 DCHECK(!initialized_
);
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
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(
366 base::Bind(&RemovableDeviceNotificationsLinux::OnFilePathChanged
,
367 base::Unretained(this)));
369 LOG(ERROR
) << "Adding watch for " << mtab_path_
.value() << " failed";
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
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();
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
427 for (std::list
<FilePath
>::const_iterator it
=
428 multiple_mounted_devices_needing_reattachment
.begin();
429 it
!= multiple_mounted_devices_needing_reattachment
.end();
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
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
;
470 std::string unique_id
;
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())
482 bool has_dcim
= IsMediaDevice(mount_point
.value());
483 MediaStorageUtil::Type type
;
486 type
= MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM
;
488 type
= MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM
;
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
;
505 base::SystemMonitor::Get()->ProcessRemovableStorageAttached(
506 device_id
, GetDisplayNameForDevice(partition_size_in_bytes
, name
),
507 mount_point
.value());
511 } // namespace chrome