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

Side by Side Diff: chrome/browser/media_gallery/media_device_notifications_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, 4 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 // MediaDeviceNotificationsLinux implementation.
6
7 #include "chrome/browser/media_gallery/media_device_notifications_linux.h"
8
9 #include <libudev.h>
10 #include <mntent.h>
11 #include <stdio.h>
12
13 #include <vector>
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 {
28
29 // List of file systems we care about.
30 const char* const kKnownFileSystems[] = {
31 "ext2",
32 "ext3",
33 "ext4",
34 "fat",
35 "hfsplus",
36 "iso9660",
37 "msdos",
38 "ntfs",
39 "udf",
40 "vfat",
41 };
42
43 // udev device property constants.
44 const char kDevName[] = "DEVNAME";
45 const char kFsUUID[] = "ID_FS_UUID";
46 const char kLabel[] = "ID_FS_LABEL";
47 const char kModel[] = "ID_MODEL";
48 const char kModelID[] = "ID_MODEL_ID";
49 const char kSerial[] = "ID_SERIAL";
50 const char kSerialShort[] = "ID_SERIAL_SHORT";
51 const char kVendor[] = "ID_VENDOR";
52 const char kVendorID[] = "ID_VENDOR_ID";
53
54 // Delimiter constants.
55 const char kNonSpaceDelim[] = ":";
56 const char kSpaceDelim[] = " ";
57
58 // Unique id prefix constants.
59 const char kFSUniqueIdPrefix[] = "UUID:";
60 const char kVendorModelSerialPrefix[] = "VendorModelSerial:";
61
62 // Device mount point details.
63 struct MountPointEntryInfo {
64 std::string mount_point;
65 int entry_pos;
66 };
67
68 // ScopedGenericObj functor for UdevObjectRelease().
69 class ScopedReleaseUdevObject {
70 public:
71 void operator()(struct udev* udev) const {
72 udev_unref(udev);
73 }
74 };
75
76 // ScopedGenericObj functor for UdevDeviceObjectRelease().
77 class ScopedReleaseUdevDeviceObject {
78 public:
79 void operator()(struct udev_device* device) const {
80 udev_device_unref(device);
81 }
82 };
83
84 // Get the device information using udev library.
85 // On success, returns true and fill in |id| and |name|.
86 bool GetDeviceInfo(const std::string& device_path, std::string* id,
87 string16* name) {
88 DCHECK(!device_path.empty());
89
90 ScopedGenericObj<struct udev*, ScopedReleaseUdevObject> udev_obj(udev_new());
91 if (!udev_obj.get())
92 return false;
93
94 struct stat device_stat;
95 if (stat(device_path.c_str(), &device_stat) < 0)
96 return false;
97
98 char device_type;
99 if (S_ISCHR(device_stat.st_mode))
100 device_type = 'c';
101 else if (S_ISBLK(device_stat.st_mode))
102 device_type = 'b';
103 else
104 return false; // Not a supported type.
105
106 ScopedGenericObj<struct udev_device*, ScopedReleaseUdevDeviceObject>
107 device(udev_device_new_from_devnum(udev_obj, device_type,
108 device_stat.st_rdev));
109 if (!device.get())
110 return false;
111
112 // Construct a device name using label or manufacturer(vendor and model)
113 // details.
114 if (name) {
115 std::string device_label;
116 const char* device_name = NULL;
117 if ((device_name = udev_device_get_property_value(device, kLabel)) ||
118 (device_name = udev_device_get_property_value(device, kSerial))) {
119 device_label = device_name;
120 } else {
121 // Format: VendorInfo ModelInfo
122 // E.g.: KnCompany Model2010
123 const char* vendor_name = NULL;
124 if ((vendor_name = udev_device_get_property_value(device, kVendor)))
125 device_label = vendor_name;
126
127 const char* model_name = NULL;
128 if ((model_name = udev_device_get_property_value(device, kModel))) {
129 if (!device_label.empty())
130 device_label += kSpaceDelim;
131 device_label += model_name;
132 }
133 }
134
135 if (IsStringUTF8(device_label))
136 *name = UTF8ToUTF16(device_label);
137 }
138
139 if (id) {
140 std::string unique_id;
141 const char* uuid = NULL;
142 if ((uuid = udev_device_get_property_value(device, kFsUUID))) {
143 unique_id = std::string(kFSUniqueIdPrefix) + uuid;
144 } else {
145 // If one of the vendor, model, serial information is missing, its value
146 // in the string is empty.
147 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
148 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
149 const char* vendor = udev_device_get_property_value(device, kVendorID);
150 const char* model = udev_device_get_property_value(device, kModelID);
151 const char* serial_short = udev_device_get_property_value(device,
152 kSerialShort);
153 if (!vendor && !model && !serial_short)
154 return false;
155
156 unique_id = std::string(kVendorModelSerialPrefix) +
157 (vendor ? vendor : "") + kNonSpaceDelim +
158 (model ? model : "") + kNonSpaceDelim +
159 (serial_short ? serial_short : "");
160 }
161 *id = chrome::MediaStorageUtil::MakeDeviceId(
162 chrome::MediaStorageUtil::USB_MASS_STORAGE_WITH_DCIM, unique_id);
163 }
164
165 return true;
166 }
167
168 } // namespace
169
170 namespace chrome {
171
172 using base::SystemMonitor;
173 using content::BrowserThread;
174
175 MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
176 const FilePath& path)
177 : initialized_(false),
178 mtab_path_(path),
179 get_device_info_func_(&GetDeviceInfo) {
180 }
181
182 MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
183 const FilePath& path,
184 GetDeviceInfoFunc get_device_info_func)
185 : initialized_(false),
186 mtab_path_(path),
187 get_device_info_func_(get_device_info_func) {
188 }
189
190 MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
192 }
193
194 void MediaDeviceNotificationsLinux::Init() {
195 DCHECK(!mtab_path_.empty());
196
197 // Put |kKnownFileSystems| in std::set to get O(log N) access time.
198 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i)
199 known_file_systems_.insert(kKnownFileSystems[i]);
200
201 BrowserThread::PostTask(
202 BrowserThread::FILE, FROM_HERE,
203 base::Bind(&MediaDeviceNotificationsLinux::InitOnFileThread, this));
204 }
205
206 void MediaDeviceNotificationsLinux::OnFilePathChanged(const FilePath& path,
207 bool error) {
208 if (path != mtab_path_) {
209 // This cannot happen unless FilePathWatcher is buggy. Just ignore this
210 // notification and do nothing.
211 NOTREACHED();
212 return;
213 }
214 if (error) {
215 LOG(ERROR) << "Error watching " << mtab_path_.value();
216 return;
217 }
218
219 UpdateMtab();
220 }
221
222 void MediaDeviceNotificationsLinux::InitOnFileThread() {
223 DCHECK(!initialized_);
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
225 initialized_ = true;
226
227 // The callback passed to Watch() has to be unretained. Otherwise
228 // MediaDeviceNotificationsLinux will live longer than expected, and
229 // FilePathWatcher will get in trouble at shutdown time.
230 bool ret = file_watcher_.Watch(
231 mtab_path_,
232 base::Bind(&MediaDeviceNotificationsLinux::OnFilePathChanged,
233 base::Unretained(this)));
234 if (!ret) {
235 LOG(ERROR) << "Adding watch for " << mtab_path_.value() << " failed";
236 return;
237 }
238
239 UpdateMtab();
240 }
241
242 void MediaDeviceNotificationsLinux::UpdateMtab() {
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
244
245 MountPointDeviceMap new_mtab;
246 ReadMtab(&new_mtab);
247
248 // Check existing mtab entries for unaccounted mount points.
249 // These mount points must have been removed in the new mtab.
250 std::vector<std::string> mount_points_to_erase;
251 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
252 old_iter != mount_info_map_.end(); ++old_iter) {
253 const std::string& mount_point = old_iter->first;
254 const std::string& mount_device = old_iter->second.mount_device;
255 MountPointDeviceMap::iterator new_iter = new_mtab.find(mount_point);
256 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
257 // |mount_point|.
258 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
259 RemoveOldDevice(old_iter->second.device_id);
260 mount_points_to_erase.push_back(mount_point);
261 }
262 }
263 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
264 // using the iterator is slightly more efficient, but more tricky, since
265 // calling std::map::erase() on an iterator invalidates it.
266 for (size_t i = 0; i < mount_points_to_erase.size(); ++i)
267 mount_info_map_.erase(mount_points_to_erase[i]);
268
269 // Check new mtab entries against existing ones.
270 for (MountPointDeviceMap::iterator new_iter = new_mtab.begin();
271 new_iter != new_mtab.end(); ++new_iter) {
272 const std::string& mount_point = new_iter->first;
273 const std::string& mount_device = new_iter->second;
274 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
275 if (old_iter == mount_info_map_.end() ||
276 old_iter->second.mount_device != mount_device) {
277 // New mount point found or an existing mount point found with a new
278 // device.
279 CheckAndAddMediaDevice(mount_device, mount_point);
280 }
281 }
282 }
283
284 void MediaDeviceNotificationsLinux::ReadMtab(MountPointDeviceMap* mtab) {
285 FILE* fp = setmntent(mtab_path_.value().c_str(), "r");
286 if (!fp)
287 return;
288
289 mntent entry;
290 char buf[512];
291
292 // Keep track of mount point entry positions in mtab file.
293 int entry_pos = 0;
294
295 // Helper map to store the device mount point details.
296 // (mount device, MountPointEntryInfo)
297 typedef std::map<std::string, MountPointEntryInfo> DeviceMap;
298 DeviceMap device_map;
299 while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
300 // We only care about real file systems.
301 if (!ContainsKey(known_file_systems_, entry.mnt_type))
302 continue;
303
304 // Add entries, but overwrite entries for the same mount device. Keep track
305 // of the entry positions in |entry_info| and use that below to resolve
306 // multiple devices mounted at the same mount point.
307 MountPointEntryInfo entry_info;
308 entry_info.mount_point = entry.mnt_dir;
309 entry_info.entry_pos = entry_pos++;
310 device_map[entry.mnt_fsname] = entry_info;
311 }
312 endmntent(fp);
313
314 // Helper map to store mount point entries.
315 // (mount point, entry position in mtab file)
316 typedef std::map<std::string, int> MountPointsInfoMap;
317 MountPointsInfoMap mount_points_info_map;
318 MountPointDeviceMap& new_mtab = *mtab;
319 for (DeviceMap::const_iterator device_it = device_map.begin();
320 device_it != device_map.end();
321 ++device_it) {
322 // Add entries, but overwrite entries for the same mount point. Keep track
323 // of the entry positions and use that information to resolve multiple
324 // devices mounted at the same mount point.
325 const std::string& mount_device = device_it->first;
326 const std::string& mount_point = device_it->second.mount_point;
327 const int entry_pos = device_it->second.entry_pos;
328 MountPointDeviceMap::iterator new_it = new_mtab.find(mount_point);
329 MountPointsInfoMap::iterator mount_point_info_map_it =
330 mount_points_info_map.find(mount_point);
331
332 // New mount point entry found or there is already a device mounted at
333 // |mount_point| and the existing mount entry is older than the current one.
334 if (new_it == new_mtab.end() ||
335 (mount_point_info_map_it != mount_points_info_map.end() &&
336 mount_point_info_map_it->second < entry_pos)) {
337 new_mtab[mount_point] = mount_device;
338 mount_points_info_map[mount_point] = entry_pos;
339 }
340 }
341 }
342
343 void MediaDeviceNotificationsLinux::CheckAndAddMediaDevice(
344 const std::string& mount_device,
345 const std::string& mount_point) {
346 if (!IsMediaDevice(mount_point))
347 return;
348
349 std::string id;
350 string16 name;
351 bool result = (*get_device_info_func_)(mount_device, &id, &name);
352
353 // Keep track of GetDeviceInfo result, to see how often we fail to get device
354 // details.
355 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_info_available",
356 result);
357 if (!result)
358 return;
359
360 // Keep track of device uuid, to see how often we receive empty values.
361 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_uuid_available",
362 !id.empty());
363 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.device_name_available",
364 !name.empty());
365
366 if (id.empty() || name.empty())
367 return;
368
369 MountDeviceAndId mount_device_and_id;
370 mount_device_and_id.mount_device = mount_device;
371 mount_device_and_id.device_id = id;
372 mount_info_map_[mount_point] = mount_device_and_id;
373
374 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
375 system_monitor->ProcessMediaDeviceAttached(id, name, mount_point);
376 }
377
378 void MediaDeviceNotificationsLinux::RemoveOldDevice(
379 const std::string& device_id) {
380 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
381 system_monitor->ProcessMediaDeviceDetached(device_id);
382 }
383
384 } // namespace chrome
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698