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