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

Side by Side 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, 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 | Annotate | Revision Log
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 // 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698