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 // 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 | |
OLD | NEW |