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

Side by Side Diff: chrome/browser/media_gallery/removable_device_notifications_linux.cc

Issue 10919051: Move device notification impl out of chrome/browser/media_gallery (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: modify for chromeos and win Created 8 years, 3 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 unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 // RemovableDeviceNotificationsLinux implementation.
6
7 #include "chrome/browser/media_gallery/removable_device_notifications_linux.h"
8
9 #include <libudev.h>
10 #include <mntent.h>
11 #include <stdio.h>
12
13 #include <list>
14
15 #include "base/bind.h"
16 #include "base/file_path.h"
17 #include "base/memory/scoped_generic_obj.h"
18 #include "base/metrics/histogram.h"
19 #include "base/stl_util.h"
20 #include "base/string_number_conversions.h"
21 #include "base/string_util.h"
22 #include "base/stringprintf.h"
23 #include "base/system_monitor/system_monitor.h"
24 #include "base/utf_string_conversions.h"
25 #include "chrome/browser/media_gallery/media_device_notifications_utils.h"
26 #include "chrome/browser/media_gallery/media_gallery_constants.h"
27 #include "chrome/browser/media_gallery/media_storage_util.h"
28
29 namespace chrome {
30
31 using base::SystemMonitor;
32 using content::BrowserThread;
33
34 namespace {
35
36 static RemovableDeviceNotificationsLinux*
37 g_removable_device_notifications_linux = NULL;
38
39 // List of file systems we care about.
40 const char* const kKnownFileSystems[] = {
41 "ext2",
42 "ext3",
43 "ext4",
44 "fat",
45 "hfsplus",
46 "iso9660",
47 "msdos",
48 "ntfs",
49 "udf",
50 "vfat",
51 };
52
53 // udev device property constants.
54 const char kBlockSubsystemKey[] = "block";
55 const char kDevName[] = "DEVNAME";
56 const char kDiskDeviceTypeKey[] = "disk";
57 const char kFsUUID[] = "ID_FS_UUID";
58 const char kLabel[] = "ID_FS_LABEL";
59 const char kModel[] = "ID_MODEL";
60 const char kModelID[] = "ID_MODEL_ID";
61 const char kRemovableSysAttr[] = "removable";
62 const char kSerial[] = "ID_SERIAL";
63 const char kSerialShort[] = "ID_SERIAL_SHORT";
64 const char kVendor[] = "ID_VENDOR";
65 const char kVendorID[] = "ID_VENDOR_ID";
66
67 // (mount point, mount device)
68 // A mapping from mount point to mount device, as extracted from the mtab
69 // file.
70 typedef std::map<FilePath, FilePath> MountPointDeviceMap;
71
72 // Reads mtab file entries into |mtab|.
73 void ReadMtab(const FilePath& mtab_path,
74 const std::set<std::string>& interesting_file_systems,
75 MountPointDeviceMap* mtab) {
76 mtab->clear();
77
78 FILE* fp = setmntent(mtab_path.value().c_str(), "r");
79 if (!fp)
80 return;
81
82 mntent entry;
83 char buf[512];
84
85 // We return the same device mounted to multiple locations, but hide
86 // devices that have been mounted over.
87 while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
88 // We only care about real file systems.
89 if (!ContainsKey(interesting_file_systems, entry.mnt_type))
90 continue;
91
92 (*mtab)[FilePath(entry.mnt_dir)] = FilePath(entry.mnt_fsname);
93 }
94 endmntent(fp);
95 }
96
97 // ScopedGenericObj functor for UdevObjectRelease().
98 class ScopedReleaseUdevObject {
99 public:
100 void operator()(struct udev* udev) const {
101 udev_unref(udev);
102 }
103 };
104 typedef ScopedGenericObj<struct udev*,
105 ScopedReleaseUdevObject> ScopedUdevObject;
106
107 // ScopedGenericObj functor for UdevDeviceObjectRelease().
108 class ScopedReleaseUdevDeviceObject {
109 public:
110 void operator()(struct udev_device* device) const {
111 udev_device_unref(device);
112 }
113 };
114 typedef ScopedGenericObj<struct udev_device*,
115 ScopedReleaseUdevDeviceObject> ScopedUdevDeviceObject;
116
117 // Wrapper function for udev_device_get_property_value() that also checks for
118 // valid but empty values.
119 std::string GetUdevDevicePropertyValue(struct udev_device* udev_device,
120 const char* key) {
121 const char* value = udev_device_get_property_value(udev_device, key);
122 if (!value)
123 return std::string();
124 return (strlen(value) > 0) ? value : std::string();
125 }
126
127 // Construct a device id using label or manufacturer (vendor and model) details.
128 std::string MakeDeviceUniqueId(struct udev_device* device) {
129 std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
130 if (!uuid.empty())
131 return kFSUniqueIdPrefix + uuid;
132
133 // If one of the vendor, model, serial information is missing, its value
134 // in the string is empty.
135 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
136 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
137 std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
138 std::string model = GetUdevDevicePropertyValue(device, kModelID);
139 std::string serial_short = GetUdevDevicePropertyValue(device,
140 kSerialShort);
141 if (vendor.empty() && model.empty() && serial_short.empty())
142 return std::string();
143
144 return base::StringPrintf("%s%s%s%s%s%s",
145 kVendorModelSerialPrefix,
146 vendor.c_str(), kNonSpaceDelim,
147 model.c_str(), kNonSpaceDelim,
148 serial_short.c_str());
149 }
150
151 // Get the device information using udev library.
152 // On success, returns true and fill in |unique_id|, |name|, and |removable|.
153 bool GetDeviceInfo(const FilePath& device_path, std::string* unique_id,
154 string16* name, bool* removable) {
155 DCHECK(!device_path.empty());
156
157 ScopedUdevObject udev_obj(udev_new());
158 if (!udev_obj.get())
159 return false;
160
161 struct stat device_stat;
162 if (stat(device_path.value().c_str(), &device_stat) < 0)
163 return false;
164
165 char device_type;
166 if (S_ISCHR(device_stat.st_mode))
167 device_type = 'c';
168 else if (S_ISBLK(device_stat.st_mode))
169 device_type = 'b';
170 else
171 return false; // Not a supported type.
172
173 ScopedUdevDeviceObject device(
174 udev_device_new_from_devnum(udev_obj, device_type, device_stat.st_rdev));
175 if (!device.get())
176 return false;
177
178 // Construct a device name using label or manufacturer (vendor and model)
179 // details.
180 if (name) {
181 std::string device_label = GetUdevDevicePropertyValue(device, kLabel);
182 if (device_label.empty())
183 device_label = GetUdevDevicePropertyValue(device, kSerial);
184 if (device_label.empty()) {
185 // Format: VendorInfo ModelInfo
186 // E.g.: KnCompany Model2010
187 std::string vendor_name = GetUdevDevicePropertyValue(device, kVendor);
188 std::string model_name = GetUdevDevicePropertyValue(device, kModel);
189 if (vendor_name.empty() && model_name.empty())
190 return false;
191
192 if (vendor_name.empty())
193 device_label = model_name;
194 else if (model_name.empty())
195 device_label = vendor_name;
196 else
197 device_label = vendor_name + kSpaceDelim + model_name;
198 }
199
200 if (IsStringUTF8(device_label))
201 *name = UTF8ToUTF16(device_label);
202 }
203
204 if (unique_id) {
205 *unique_id = MakeDeviceUniqueId(device);
206 if (unique_id->empty())
207 return false;
208 }
209
210 if (removable) {
211 const char* value = udev_device_get_sysattr_value(device,
212 kRemovableSysAttr);
213 if (!value) {
214 // |parent_device| is owned by |device| and does not need to be cleaned
215 // up.
216 struct udev_device* parent_device =
217 udev_device_get_parent_with_subsystem_devtype(device,
218 kBlockSubsystemKey,
219 kDiskDeviceTypeKey);
220 value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
221 }
222 *removable = (value && atoi(value) == 1);
223 }
224 return true;
225 }
226
227 } // namespace
228
229 RemovableDeviceNotificationsLinux::MountPointInfo::MountPointInfo() {
230 }
231
232 RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
233 const FilePath& path)
234 : initialized_(false),
235 mtab_path_(path),
236 get_device_info_func_(&GetDeviceInfo) {
237 DCHECK(!g_removable_device_notifications_linux);
238 g_removable_device_notifications_linux = this;
239 }
240
241 RemovableDeviceNotificationsLinux::RemovableDeviceNotificationsLinux(
242 const FilePath& path,
243 GetDeviceInfoFunc get_device_info_func)
244 : initialized_(false),
245 mtab_path_(path),
246 get_device_info_func_(get_device_info_func) {
247 DCHECK(!g_removable_device_notifications_linux);
248 g_removable_device_notifications_linux = this;
249 }
250
251 RemovableDeviceNotificationsLinux::~RemovableDeviceNotificationsLinux() {
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
253 DCHECK_EQ(this, g_removable_device_notifications_linux);
254 g_removable_device_notifications_linux = NULL;
255 }
256
257 // static
258 RemovableDeviceNotificationsLinux*
259 RemovableDeviceNotificationsLinux::GetInstance() {
260 DCHECK(g_removable_device_notifications_linux != NULL);
261 return g_removable_device_notifications_linux;
262 }
263
264 void RemovableDeviceNotificationsLinux::Init() {
265 DCHECK(!mtab_path_.empty());
266
267 // Put |kKnownFileSystems| in std::set to get O(log N) access time.
268 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i)
269 known_file_systems_.insert(kKnownFileSystems[i]);
270
271 BrowserThread::PostTask(
272 BrowserThread::FILE, FROM_HERE,
273 base::Bind(&RemovableDeviceNotificationsLinux::InitOnFileThread, this));
274 }
275
276 FilePath RemovableDeviceNotificationsLinux::GetDeviceMountPoint(
277 const std::string& device_id) const {
278
279 MediaStorageUtil::Type type;
280 MediaStorageUtil::CrackDeviceId(device_id, &type, NULL);
281 if (type == MediaStorageUtil::MTP_OR_PTP)
282 return FilePath();
283 DCHECK(type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM ||
284 type == MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM ||
285 type == MediaStorageUtil::FIXED_MASS_STORAGE);
286
287 FilePath mount_device;
288 for (MountMap::const_iterator it = mount_info_map_.begin();
289 it != mount_info_map_.end();
290 ++it) {
291 if (it->second.device_id == device_id) {
292 mount_device = it->second.mount_device;
293 break;
294 }
295 }
296 if (mount_device.empty())
297 return mount_device;
298
299 const ReferencedMountPoint& referenced_info =
300 mount_priority_map_.find(mount_device)->second;
301 for (ReferencedMountPoint::const_iterator it = referenced_info.begin();
302 it != referenced_info.end();
303 ++it) {
304 if (it->second)
305 return it->first;
306 }
307 // If none of them are default, just return the first.
308 return FilePath(referenced_info.begin()->first);
309 }
310
311 bool RemovableDeviceNotificationsLinux::GetDeviceInfoForPath(
312 const FilePath& path,
313 SystemMonitor::RemovableStorageInfo* device_info) const {
314 if (!path.IsAbsolute())
315 return false;
316
317 FilePath current = path;
318 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
319 current = current.DirName();
320
321 MountMap::const_iterator mount_info = mount_info_map_.find(current);
322 if (mount_info == mount_info_map_.end())
323 return false;
324
325 if (device_info) {
326 device_info->device_id = mount_info->second.device_id;
327 device_info->name = mount_info->second.device_name;
328 device_info->location = current.value();
329 }
330 return true;
331 }
332
333 void RemovableDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path,
334 bool error) {
335 if (path != mtab_path_) {
336 // This cannot happen unless FilePathWatcher is buggy. Just ignore this
337 // notification and do nothing.
338 NOTREACHED();
339 return;
340 }
341 if (error) {
342 LOG(ERROR) << "Error watching " << mtab_path_.value();
343 return;
344 }
345
346 UpdateMtab();
347 }
348
349 void RemovableDeviceNotificationsLinux::InitOnFileThread() {
350 DCHECK(!initialized_);
351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
352 initialized_ = true;
353
354 // The callback passed to Watch() has to be unretained. Otherwise
355 // RemovableDeviceNotificationsLinux will live longer than expected, and
356 // FilePathWatcher will get in trouble at shutdown time.
357 bool ret = file_watcher_.Watch(
358 mtab_path_,
359 base::Bind(&RemovableDeviceNotificationsLinux::OnFilePathChanged,
360 base::Unretained(this)));
361 if (!ret) {
362 LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
363 return;
364 }
365
366 UpdateMtab();
367 }
368
369 void RemovableDeviceNotificationsLinux::UpdateMtab() {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
371
372 MountPointDeviceMap new_mtab;
373 ReadMtab(mtab_path_, known_file_systems_, &new_mtab);
374
375 // Check existing mtab entries for unaccounted mount points.
376 // These mount points must have been removed in the new mtab.
377 std::list<FilePath> mount_points_to_erase;
378 std::list<FilePath> multiple_mounted_devices_needing_reattachment;
379 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
380 old_iter != mount_info_map_.end(); ++old_iter) {
381 const FilePath& mount_point = old_iter->first;
382 const FilePath& mount_device = old_iter->second.mount_device;
383 MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point);
384 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
385 // |mount_point|.
386 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
387 MountPriorityMap::iterator priority =
388 mount_priority_map_.find(mount_device);
389 DCHECK(priority != mount_priority_map_.end());
390 ReferencedMountPoint::const_iterator has_priority =
391 priority->second.find(mount_point);
392 if (MediaStorageUtil::IsRemovableDevice(old_iter->second.device_id)) {
393 DCHECK(has_priority != priority->second.end());
394 if (has_priority->second) {
395 SystemMonitor::Get()->ProcessRemovableStorageDetached(
396 old_iter->second.device_id);
397 }
398 if (priority->second.size() > 1)
399 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
400 }
401 priority->second.erase(mount_point);
402 if (priority->second.empty())
403 mount_priority_map_.erase(mount_device);
404 mount_points_to_erase.push_back(mount_point);
405 }
406 }
407
408 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
409 // using the iterator is slightly more efficient, but more tricky, since
410 // calling std::map::erase() on an iterator invalidates it.
411 for (std::list<FilePath>::const_iterator it = mount_points_to_erase.begin();
412 it != mount_points_to_erase.end();
413 ++it) {
414 mount_info_map_.erase(*it);
415 }
416
417 // For any multiply mounted device where the mount that we had notified
418 // got detached, send a notification of attachment for one of the other
419 // mount points.
420 for (std::list<FilePath>::const_iterator it =
421 multiple_mounted_devices_needing_reattachment.begin();
422 it != multiple_mounted_devices_needing_reattachment.end();
423 ++it) {
424 ReferencedMountPoint::iterator first_mount_point_info =
425 mount_priority_map_.find(*it)->second.begin();
426 const FilePath& mount_point = first_mount_point_info->first;
427 first_mount_point_info->second = true;
428
429 const MountPointInfo& mount_info =
430 mount_info_map_.find(mount_point)->second;
431 DCHECK(MediaStorageUtil::IsRemovableDevice(mount_info.device_id));
432 base::SystemMonitor::Get()->ProcessRemovableStorageAttached(
433 mount_info.device_id, mount_info.device_name, mount_point.value());
434 }
435
436 // Check new mtab entries against existing ones.
437 for (MountPointDeviceMap::iterator new_iter = new_mtab.begin();
438 new_iter != new_mtab.end(); ++new_iter) {
439 const FilePath& mount_point = new_iter->first;
440 const FilePath& mount_device = new_iter->second;
441 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
442 if (old_iter == mount_info_map_.end() ||
443 old_iter->second.mount_device != mount_device) {
444 // New mount point found or an existing mount point found with a new
445 // device.
446 AddNewMount(mount_device, mount_point);
447 }
448 }
449 }
450
451 void RemovableDeviceNotificationsLinux::AddNewMount(
452 const FilePath& mount_device, const FilePath& mount_point) {
453 MountPriorityMap::iterator priority =
454 mount_priority_map_.find(mount_device);
455 if (priority != mount_priority_map_.end()) {
456 const FilePath& other_mount_point = priority->second.begin()->first;
457 priority->second[mount_point] = false;
458 mount_info_map_[mount_point] =
459 mount_info_map_.find(other_mount_point)->second;
460 return;
461 }
462
463 std::string unique_id;
464 string16 name;
465 bool removable;
466 bool result = get_device_info_func_(mount_device, &unique_id, &name,
467 &removable);
468
469 // Keep track of GetDeviceInfo result, to see how often we fail to get device
470 // details.
471 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available",
472 result);
473 if (!result)
474 return;
475
476 // Keep track of device uuid, to see how often we receive empty values.
477 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available",
478 !unique_id.empty());
479 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available",
480 !name.empty());
481
482 if (unique_id.empty() || name.empty())
483 return;
484
485 bool has_dcim = IsMediaDevice(mount_point.value());
486 MediaStorageUtil::Type type;
487 if (removable) {
488 if (has_dcim) {
489 type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_WITH_DCIM;
490 } else {
491 type = MediaStorageUtil::REMOVABLE_MASS_STORAGE_NO_DCIM;
492 }
493 } else {
494 type = MediaStorageUtil::FIXED_MASS_STORAGE;
495 }
496 std::string device_id = MediaStorageUtil::MakeDeviceId(type, unique_id);
497
498 MountPointInfo mount_point_info;
499 mount_point_info.mount_device = mount_device;
500 mount_point_info.device_id = device_id;
501 mount_point_info.device_name = name;
502
503 mount_info_map_[mount_point] = mount_point_info;
504 mount_priority_map_[mount_device][mount_point] = removable;
505
506 if (removable) {
507 SystemMonitor::Get()->ProcessRemovableStorageAttached(device_id, name,
508 mount_point.value());
509 }
510 }
511
512 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698