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

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

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

Powered by Google App Engine
This is Rietveld 408576698