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.
5 // StorageMonitorLinux implementation.
7 #include "chrome/browser/storage_monitor/storage_monitor_linux.h"
14 #include "base/basictypes.h"
15 #include "base/bind.h"
16 #include "base/metrics/histogram.h"
17 #include "base/process/kill.h"
18 #include "base/process/launch.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "chrome/browser/storage_monitor/media_storage_util.h"
24 #include "chrome/browser/storage_monitor/media_transfer_protocol_device_observer_linux.h"
25 #include "chrome/browser/storage_monitor/removable_device_constants.h"
26 #include "chrome/browser/storage_monitor/storage_info.h"
27 #include "chrome/browser/storage_monitor/udev_util_linux.h"
28 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
30 using content::BrowserThread;
31 typedef MtabWatcherLinux::MountPointDeviceMap MountPointDeviceMap;
35 // udev device property constants.
36 const char kBlockSubsystemKey[] = "block";
37 const char kDiskDeviceTypeKey[] = "disk";
38 const char kFsUUID[] = "ID_FS_UUID";
39 const char kLabel[] = "ID_FS_LABEL";
40 const char kModel[] = "ID_MODEL";
41 const char kModelID[] = "ID_MODEL_ID";
42 const char kRemovableSysAttr[] = "removable";
43 const char kSerialShort[] = "ID_SERIAL_SHORT";
44 const char kSizeSysAttr[] = "size";
45 const char kVendor[] = "ID_VENDOR";
46 const char kVendorID[] = "ID_VENDOR_ID";
48 // Construct a device id using label or manufacturer (vendor and model) details.
49 std::string MakeDeviceUniqueId(struct udev_device* device) {
50 std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
51 // Keep track of device uuid, to see how often we receive empty uuid values.
52 UMA_HISTOGRAM_BOOLEAN(
53 "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
57 return kFSUniqueIdPrefix + uuid;
59 // If one of the vendor, model, serial information is missing, its value
60 // in the string is empty.
61 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
62 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
63 std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
64 std::string model = GetUdevDevicePropertyValue(device, kModelID);
65 std::string serial_short = GetUdevDevicePropertyValue(device,
67 if (vendor.empty() && model.empty() && serial_short.empty())
70 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
73 // Records GetDeviceInfo result on destruction, to see how often we fail to get
75 class ScopedGetDeviceInfoResultRecorder {
77 ScopedGetDeviceInfoResultRecorder() : result_(false) {}
78 ~ScopedGetDeviceInfoResultRecorder() {
79 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
83 void set_result(bool result) {
90 DISALLOW_COPY_AND_ASSIGN(ScopedGetDeviceInfoResultRecorder);
93 // Returns the storage partition size of the device specified by |device_path|.
94 // If the requested information is unavailable, returns 0.
95 uint64 GetDeviceStorageSize(const base::FilePath& device_path,
96 struct udev_device* device) {
97 // sysfs provides the device size in units of 512-byte blocks.
98 const std::string partition_size = udev_device_get_sysattr_value(
99 device, kSizeSysAttr);
101 // Keep track of device size, to see how often this information is
103 UMA_HISTOGRAM_BOOLEAN(
104 "RemovableDeviceNotificationsLinux.device_partition_size_available",
105 !partition_size.empty());
107 uint64 total_size_in_bytes = 0;
108 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
110 return (total_size_in_bytes <= kuint64max / 512) ?
111 total_size_in_bytes * 512 : 0;
114 // Gets the device information using udev library.
115 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
116 const base::FilePath& mount_point) {
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
118 DCHECK(!device_path.empty());
120 scoped_ptr<StorageInfo> storage_info;
122 ScopedGetDeviceInfoResultRecorder results_recorder;
124 ScopedUdevObject udev_obj(udev_new());
126 return storage_info.Pass();
128 struct stat device_stat;
129 if (stat(device_path.value().c_str(), &device_stat) < 0)
130 return storage_info.Pass();
133 if (S_ISCHR(device_stat.st_mode))
135 else if (S_ISBLK(device_stat.st_mode))
138 return storage_info.Pass(); // Not a supported type.
140 ScopedUdevDeviceObject device(
141 udev_device_new_from_devnum(udev_obj.get(), device_type,
142 device_stat.st_rdev));
144 return storage_info.Pass();
146 base::string16 volume_label =
147 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kLabel));
148 base::string16 vendor_name =
149 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kVendor));
150 base::string16 model_name =
151 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kModel));
153 std::string unique_id = MakeDeviceUniqueId(device.get());
155 // Keep track of device info details to see how often we get invalid values.
156 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, volume_label);
159 udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
161 // |parent_device| is owned by |device| and does not need to be cleaned
163 struct udev_device* parent_device =
164 udev_device_get_parent_with_subsystem_devtype(device.get(),
167 value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
169 const bool is_removable = (value && atoi(value) == 1);
171 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
173 if (MediaStorageUtil::HasDcim(mount_point))
174 type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
176 type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
179 results_recorder.set_result(true);
181 storage_info.reset(new StorageInfo(
182 StorageInfo::MakeDeviceId(type, unique_id),
188 GetDeviceStorageSize(device_path, device.get())));
189 return storage_info.Pass();
192 MtabWatcherLinux* CreateMtabWatcherLinuxOnFileThread(
193 const base::FilePath& mtab_path,
194 base::WeakPtr<MtabWatcherLinux::Delegate> delegate) {
195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
197 return new MtabWatcherLinux(mtab_path, delegate);
200 StorageMonitor::EjectStatus EjectPathOnFileThread(
201 const base::FilePath& path,
202 const base::FilePath& device) {
203 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
205 // Note: Linux LSB says umount should exist in /bin.
206 static const char kUmountBinary[] = "/bin/umount";
207 std::vector<std::string> command;
208 command.push_back(kUmountBinary);
209 command.push_back(path.value());
211 base::LaunchOptions options;
212 base::ProcessHandle handle;
213 if (!base::LaunchProcess(command, options, &handle))
214 return StorageMonitor::EJECT_FAILURE;
217 if (!base::WaitForExitCodeWithTimeout(handle, &exit_code,
218 base::TimeDelta::FromMilliseconds(3000))) {
219 base::KillProcess(handle, -1, false);
220 base::EnsureProcessTerminated(handle);
221 return StorageMonitor::EJECT_FAILURE;
224 // TODO(gbillock): Make sure this is found in documentation
225 // somewhere. Experimentally it seems to hold that exit code
226 // 1 means device is in use.
228 return StorageMonitor::EJECT_IN_USE;
230 return StorageMonitor::EJECT_FAILURE;
232 return StorageMonitor::EJECT_OK;
237 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
239 get_device_info_callback_(base::Bind(&GetDeviceInfo)),
240 weak_ptr_factory_(this) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244 StorageMonitorLinux::~StorageMonitorLinux() {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
248 void StorageMonitorLinux::Init() {
249 DCHECK(!mtab_path_.empty());
251 BrowserThread::PostTaskAndReplyWithResult(
252 BrowserThread::FILE, FROM_HERE,
253 base::Bind(&CreateMtabWatcherLinuxOnFileThread,
255 weak_ptr_factory_.GetWeakPtr()),
256 base::Bind(&StorageMonitorLinux::OnMtabWatcherCreated,
257 weak_ptr_factory_.GetWeakPtr()));
259 if (!media_transfer_protocol_manager_) {
260 scoped_refptr<base::MessageLoopProxy> loop_proxy =
261 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
262 media_transfer_protocol_manager_.reset(
263 device::MediaTransferProtocolManager::Initialize(loop_proxy));
266 media_transfer_protocol_device_observer_.reset(
267 new MediaTransferProtocolDeviceObserverLinux(
268 receiver(), media_transfer_protocol_manager_.get()));
271 bool StorageMonitorLinux::GetStorageInfoForPath(
272 const base::FilePath& path,
273 StorageInfo* device_info) const {
275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
277 // TODO(thestig) |media_transfer_protocol_device_observer_| should always be
279 if (media_transfer_protocol_device_observer_ &&
280 media_transfer_protocol_device_observer_->GetStorageInfoForPath(
281 path, device_info)) {
285 if (!path.IsAbsolute())
288 base::FilePath current = path;
289 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
290 current = current.DirName();
292 MountMap::const_iterator mount_info = mount_info_map_.find(current);
293 if (mount_info == mount_info_map_.end())
295 *device_info = mount_info->second.storage_info;
299 device::MediaTransferProtocolManager*
300 StorageMonitorLinux::media_transfer_protocol_manager() {
301 return media_transfer_protocol_manager_.get();
304 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
305 const GetDeviceInfoCallback& get_device_info_callback) {
306 get_device_info_callback_ = get_device_info_callback;
309 void StorageMonitorLinux::SetMediaTransferProtocolManagerForTest(
310 device::MediaTransferProtocolManager* test_manager) {
311 DCHECK(!media_transfer_protocol_manager_);
312 media_transfer_protocol_manager_.reset(test_manager);
315 void StorageMonitorLinux::EjectDevice(
316 const std::string& device_id,
317 base::Callback<void(EjectStatus)> callback) {
318 StorageInfo::Type type;
319 if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
320 callback.Run(EJECT_FAILURE);
324 if (type == StorageInfo::MTP_OR_PTP) {
325 media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
329 // Find the mount point for the given device ID.
331 base::FilePath device;
332 for (MountMap::iterator mount_info = mount_info_map_.begin();
333 mount_info != mount_info_map_.end(); ++mount_info) {
334 if (mount_info->second.storage_info.device_id() == device_id) {
335 path = mount_info->first;
336 device = mount_info->second.mount_device;
337 mount_info_map_.erase(mount_info);
343 callback.Run(EJECT_NO_SUCH_DEVICE);
347 receiver()->ProcessDetach(device_id);
349 BrowserThread::PostTaskAndReplyWithResult(
350 BrowserThread::FILE, FROM_HERE,
351 base::Bind(&EjectPathOnFileThread, path, device),
355 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
356 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
357 mtab_watcher_.reset(watcher);
360 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
361 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
363 // Check existing mtab entries for unaccounted mount points.
364 // These mount points must have been removed in the new mtab.
365 std::list<base::FilePath> mount_points_to_erase;
366 std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
367 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
368 old_iter != mount_info_map_.end(); ++old_iter) {
369 const base::FilePath& mount_point = old_iter->first;
370 const base::FilePath& mount_device = old_iter->second.mount_device;
371 MountPointDeviceMap::const_iterator new_iter = new_mtab.find(mount_point);
372 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
374 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
375 MountPriorityMap::iterator priority =
376 mount_priority_map_.find(mount_device);
377 DCHECK(priority != mount_priority_map_.end());
378 ReferencedMountPoint::const_iterator has_priority =
379 priority->second.find(mount_point);
380 if (StorageInfo::IsRemovableDevice(
381 old_iter->second.storage_info.device_id())) {
382 DCHECK(has_priority != priority->second.end());
383 if (has_priority->second) {
384 receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
386 if (priority->second.size() > 1)
387 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
389 priority->second.erase(mount_point);
390 if (priority->second.empty())
391 mount_priority_map_.erase(mount_device);
392 mount_points_to_erase.push_back(mount_point);
396 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
397 // using the iterator is slightly more efficient, but more tricky, since
398 // calling std::map::erase() on an iterator invalidates it.
399 for (std::list<base::FilePath>::const_iterator it =
400 mount_points_to_erase.begin();
401 it != mount_points_to_erase.end();
403 mount_info_map_.erase(*it);
406 // For any multiply mounted device where the mount that we had notified
407 // got detached, send a notification of attachment for one of the other
409 for (std::list<base::FilePath>::const_iterator it =
410 multiple_mounted_devices_needing_reattachment.begin();
411 it != multiple_mounted_devices_needing_reattachment.end();
413 ReferencedMountPoint::iterator first_mount_point_info =
414 mount_priority_map_.find(*it)->second.begin();
415 const base::FilePath& mount_point = first_mount_point_info->first;
416 first_mount_point_info->second = true;
418 const StorageInfo& mount_info =
419 mount_info_map_.find(mount_point)->second.storage_info;
420 DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
421 receiver()->ProcessAttach(mount_info);
424 // Check new mtab entries against existing ones.
425 for (MountPointDeviceMap::const_iterator new_iter = new_mtab.begin();
426 new_iter != new_mtab.end(); ++new_iter) {
427 const base::FilePath& mount_point = new_iter->first;
428 const base::FilePath& mount_device = new_iter->second;
429 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
430 if (old_iter == mount_info_map_.end() ||
431 old_iter->second.mount_device != mount_device) {
432 // New mount point found or an existing mount point found with a new
434 if (IsDeviceAlreadyMounted(mount_device)) {
435 HandleDeviceMountedMultipleTimes(mount_device, mount_point);
437 BrowserThread::PostTaskAndReplyWithResult(
438 BrowserThread::FILE, FROM_HERE,
439 base::Bind(get_device_info_callback_, mount_device, mount_point),
440 base::Bind(&StorageMonitorLinux::AddNewMount,
441 weak_ptr_factory_.GetWeakPtr(),
447 // Note: relies on scheduled tasks on the file thread being sequential. This
448 // block needs to follow the for loop, so that the DoNothing call on the FILE
449 // thread happens after the scheduled metadata retrievals, meaning that the
450 // reply callback will then happen after all the AddNewMount calls.
451 if (!IsInitialized()) {
452 BrowserThread::PostTaskAndReply(
453 BrowserThread::FILE, FROM_HERE,
454 base::Bind(&base::DoNothing),
455 base::Bind(&StorageMonitorLinux::MarkInitialized,
456 weak_ptr_factory_.GetWeakPtr()));
460 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
461 const base::FilePath& mount_device) const {
462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
463 return ContainsKey(mount_priority_map_, mount_device);
466 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
467 const base::FilePath& mount_device,
468 const base::FilePath& mount_point) {
469 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
471 MountPriorityMap::iterator priority = mount_priority_map_.find(mount_device);
472 DCHECK(priority != mount_priority_map_.end());
473 const base::FilePath& other_mount_point = priority->second.begin()->first;
474 priority->second[mount_point] = false;
475 mount_info_map_[mount_point] =
476 mount_info_map_.find(other_mount_point)->second;
479 void StorageMonitorLinux::AddNewMount(const base::FilePath& mount_device,
480 scoped_ptr<StorageInfo> storage_info) {
481 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
486 DCHECK(!storage_info->device_id().empty());
488 bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
489 const base::FilePath mount_point(storage_info->location());
491 MountPointInfo mount_point_info;
492 mount_point_info.mount_device = mount_device;
493 mount_point_info.storage_info = *storage_info;
494 mount_info_map_[mount_point] = mount_point_info;
495 mount_priority_map_[mount_device][mount_point] = removable;
496 receiver()->ProcessAttach(*storage_info);
499 StorageMonitor* StorageMonitor::Create() {
500 const base::FilePath kDefaultMtabPath("/etc/mtab");
501 return new StorageMonitorLinux(kDefaultMtabPath);