Ignore title parameter for navigator.registerProtocolHandler
[chromium-blink-merge.git] / components / storage_monitor / storage_monitor_linux.cc
blob4e79fd27937628bf5e94b70ec2302ade1d2f2a8a
1 // Copyright 2014 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 "components/storage_monitor/storage_monitor_linux.h"
9 #include <mntent.h>
10 #include <stdio.h>
12 #include <list>
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 "components/storage_monitor/media_storage_util.h"
24 #include "components/storage_monitor/media_transfer_protocol_device_observer_linux.h"
25 #include "components/storage_monitor/removable_device_constants.h"
26 #include "components/storage_monitor/storage_info.h"
27 #include "components/storage_monitor/udev_util_linux.h"
28 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
30 using content::BrowserThread;
32 namespace storage_monitor {
34 typedef MtabWatcherLinux::MountPointDeviceMap MountPointDeviceMap;
36 namespace {
38 // udev device property constants.
39 const char kBlockSubsystemKey[] = "block";
40 const char kDiskDeviceTypeKey[] = "disk";
41 const char kFsUUID[] = "ID_FS_UUID";
42 const char kLabel[] = "ID_FS_LABEL";
43 const char kModel[] = "ID_MODEL";
44 const char kModelID[] = "ID_MODEL_ID";
45 const char kRemovableSysAttr[] = "removable";
46 const char kSerialShort[] = "ID_SERIAL_SHORT";
47 const char kSizeSysAttr[] = "size";
48 const char kVendor[] = "ID_VENDOR";
49 const char kVendorID[] = "ID_VENDOR_ID";
51 // Construct a device id using label or manufacturer (vendor and model) details.
52 std::string MakeDeviceUniqueId(struct udev_device* device) {
53 std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
54 // Keep track of device uuid, to see how often we receive empty uuid values.
55 UMA_HISTOGRAM_BOOLEAN(
56 "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
57 !uuid.empty());
59 if (!uuid.empty())
60 return kFSUniqueIdPrefix + uuid;
62 // If one of the vendor, model, serial information is missing, its value
63 // in the string is empty.
64 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
65 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
66 std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
67 std::string model = GetUdevDevicePropertyValue(device, kModelID);
68 std::string serial_short = GetUdevDevicePropertyValue(device,
69 kSerialShort);
70 if (vendor.empty() && model.empty() && serial_short.empty())
71 return std::string();
73 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
76 // Records GetDeviceInfo result on destruction, to see how often we fail to get
77 // device details.
78 class ScopedGetDeviceInfoResultRecorder {
79 public:
80 ScopedGetDeviceInfoResultRecorder() : result_(false) {}
81 ~ScopedGetDeviceInfoResultRecorder() {
82 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
83 result_);
86 void set_result(bool result) {
87 result_ = result;
90 private:
91 bool result_;
93 DISALLOW_COPY_AND_ASSIGN(ScopedGetDeviceInfoResultRecorder);
96 // Returns the storage partition size of the device specified by |device_path|.
97 // If the requested information is unavailable, returns 0.
98 uint64 GetDeviceStorageSize(const base::FilePath& device_path,
99 struct udev_device* device) {
100 // sysfs provides the device size in units of 512-byte blocks.
101 const std::string partition_size = udev_device_get_sysattr_value(
102 device, kSizeSysAttr);
104 // Keep track of device size, to see how often this information is
105 // unavailable.
106 UMA_HISTOGRAM_BOOLEAN(
107 "RemovableDeviceNotificationsLinux.device_partition_size_available",
108 !partition_size.empty());
110 uint64 total_size_in_bytes = 0;
111 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
112 return 0;
113 return (total_size_in_bytes <= kuint64max / 512) ?
114 total_size_in_bytes * 512 : 0;
117 // Gets the device information using udev library.
118 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
119 const base::FilePath& mount_point) {
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
121 DCHECK(!device_path.empty());
123 scoped_ptr<StorageInfo> storage_info;
125 ScopedGetDeviceInfoResultRecorder results_recorder;
127 ScopedUdevObject udev_obj(udev_new());
128 if (!udev_obj.get())
129 return storage_info.Pass();
131 struct stat device_stat;
132 if (stat(device_path.value().c_str(), &device_stat) < 0)
133 return storage_info.Pass();
135 char device_type;
136 if (S_ISCHR(device_stat.st_mode))
137 device_type = 'c';
138 else if (S_ISBLK(device_stat.st_mode))
139 device_type = 'b';
140 else
141 return storage_info.Pass(); // Not a supported type.
143 ScopedUdevDeviceObject device(
144 udev_device_new_from_devnum(udev_obj.get(), device_type,
145 device_stat.st_rdev));
146 if (!device.get())
147 return storage_info.Pass();
149 base::string16 volume_label =
150 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kLabel));
151 base::string16 vendor_name =
152 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kVendor));
153 base::string16 model_name =
154 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kModel));
156 std::string unique_id = MakeDeviceUniqueId(device.get());
158 // Keep track of device info details to see how often we get invalid values.
159 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, volume_label);
161 const char* value =
162 udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
163 if (!value) {
164 // |parent_device| is owned by |device| and does not need to be cleaned
165 // up.
166 struct udev_device* parent_device =
167 udev_device_get_parent_with_subsystem_devtype(device.get(),
168 kBlockSubsystemKey,
169 kDiskDeviceTypeKey);
170 value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
172 const bool is_removable = (value && atoi(value) == 1);
174 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
175 if (is_removable) {
176 if (MediaStorageUtil::HasDcim(mount_point))
177 type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
178 else
179 type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
182 results_recorder.set_result(true);
184 storage_info.reset(new StorageInfo(
185 StorageInfo::MakeDeviceId(type, unique_id),
186 mount_point.value(),
187 volume_label,
188 vendor_name,
189 model_name,
190 GetDeviceStorageSize(device_path, device.get())));
191 return storage_info.Pass();
194 MtabWatcherLinux* CreateMtabWatcherLinuxOnFileThread(
195 const base::FilePath& mtab_path,
196 base::WeakPtr<MtabWatcherLinux::Delegate> delegate) {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
198 // Owned by caller.
199 return new MtabWatcherLinux(mtab_path, delegate);
202 StorageMonitor::EjectStatus EjectPathOnFileThread(
203 const base::FilePath& path,
204 const base::FilePath& device) {
205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
207 // Note: Linux LSB says umount should exist in /bin.
208 static const char kUmountBinary[] = "/bin/umount";
209 std::vector<std::string> command;
210 command.push_back(kUmountBinary);
211 command.push_back(path.value());
213 base::LaunchOptions options;
214 base::ProcessHandle handle;
215 if (!base::LaunchProcess(command, options, &handle))
216 return StorageMonitor::EJECT_FAILURE;
218 int exit_code = -1;
219 if (!base::WaitForExitCodeWithTimeout(handle, &exit_code,
220 base::TimeDelta::FromMilliseconds(3000))) {
221 base::KillProcess(handle, -1, false);
222 base::EnsureProcessTerminated(handle);
223 return StorageMonitor::EJECT_FAILURE;
226 // TODO(gbillock): Make sure this is found in documentation
227 // somewhere. Experimentally it seems to hold that exit code
228 // 1 means device is in use.
229 if (exit_code == 1)
230 return StorageMonitor::EJECT_IN_USE;
231 if (exit_code != 0)
232 return StorageMonitor::EJECT_FAILURE;
234 return StorageMonitor::EJECT_OK;
237 } // namespace
239 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
240 : mtab_path_(path),
241 get_device_info_callback_(base::Bind(&GetDeviceInfo)),
242 weak_ptr_factory_(this) {
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
246 StorageMonitorLinux::~StorageMonitorLinux() {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
250 void StorageMonitorLinux::Init() {
251 DCHECK(!mtab_path_.empty());
253 BrowserThread::PostTaskAndReplyWithResult(
254 BrowserThread::FILE, FROM_HERE,
255 base::Bind(&CreateMtabWatcherLinuxOnFileThread,
256 mtab_path_,
257 weak_ptr_factory_.GetWeakPtr()),
258 base::Bind(&StorageMonitorLinux::OnMtabWatcherCreated,
259 weak_ptr_factory_.GetWeakPtr()));
261 if (!media_transfer_protocol_manager_) {
262 scoped_refptr<base::MessageLoopProxy> loop_proxy =
263 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
264 media_transfer_protocol_manager_.reset(
265 device::MediaTransferProtocolManager::Initialize(loop_proxy));
268 media_transfer_protocol_device_observer_.reset(
269 new MediaTransferProtocolDeviceObserverLinux(
270 receiver(), media_transfer_protocol_manager_.get()));
273 bool StorageMonitorLinux::GetStorageInfoForPath(
274 const base::FilePath& path,
275 StorageInfo* device_info) const {
276 DCHECK(device_info);
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
279 // TODO(thestig) |media_transfer_protocol_device_observer_| should always be
280 // valid.
281 if (media_transfer_protocol_device_observer_ &&
282 media_transfer_protocol_device_observer_->GetStorageInfoForPath(
283 path, device_info)) {
284 return true;
287 if (!path.IsAbsolute())
288 return false;
290 base::FilePath current = path;
291 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
292 current = current.DirName();
294 MountMap::const_iterator mount_info = mount_info_map_.find(current);
295 if (mount_info == mount_info_map_.end())
296 return false;
297 *device_info = mount_info->second.storage_info;
298 return true;
301 device::MediaTransferProtocolManager*
302 StorageMonitorLinux::media_transfer_protocol_manager() {
303 return media_transfer_protocol_manager_.get();
306 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
307 const GetDeviceInfoCallback& get_device_info_callback) {
308 get_device_info_callback_ = get_device_info_callback;
311 void StorageMonitorLinux::SetMediaTransferProtocolManagerForTest(
312 device::MediaTransferProtocolManager* test_manager) {
313 DCHECK(!media_transfer_protocol_manager_);
314 media_transfer_protocol_manager_.reset(test_manager);
317 void StorageMonitorLinux::EjectDevice(
318 const std::string& device_id,
319 base::Callback<void(EjectStatus)> callback) {
320 StorageInfo::Type type;
321 if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
322 callback.Run(EJECT_FAILURE);
323 return;
326 if (type == StorageInfo::MTP_OR_PTP) {
327 media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
328 return;
331 // Find the mount point for the given device ID.
332 base::FilePath path;
333 base::FilePath device;
334 for (MountMap::iterator mount_info = mount_info_map_.begin();
335 mount_info != mount_info_map_.end(); ++mount_info) {
336 if (mount_info->second.storage_info.device_id() == device_id) {
337 path = mount_info->first;
338 device = mount_info->second.mount_device;
339 mount_info_map_.erase(mount_info);
340 break;
344 if (path.empty()) {
345 callback.Run(EJECT_NO_SUCH_DEVICE);
346 return;
349 receiver()->ProcessDetach(device_id);
351 BrowserThread::PostTaskAndReplyWithResult(
352 BrowserThread::FILE, FROM_HERE,
353 base::Bind(&EjectPathOnFileThread, path, device),
354 callback);
357 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
359 mtab_watcher_.reset(watcher);
362 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
365 // Check existing mtab entries for unaccounted mount points.
366 // These mount points must have been removed in the new mtab.
367 std::list<base::FilePath> mount_points_to_erase;
368 std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
369 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
370 old_iter != mount_info_map_.end(); ++old_iter) {
371 const base::FilePath& mount_point = old_iter->first;
372 const base::FilePath& mount_device = old_iter->second.mount_device;
373 MountPointDeviceMap::const_iterator new_iter = new_mtab.find(mount_point);
374 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
375 // |mount_point|.
376 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
377 MountPriorityMap::iterator priority =
378 mount_priority_map_.find(mount_device);
379 DCHECK(priority != mount_priority_map_.end());
380 ReferencedMountPoint::const_iterator has_priority =
381 priority->second.find(mount_point);
382 if (StorageInfo::IsRemovableDevice(
383 old_iter->second.storage_info.device_id())) {
384 DCHECK(has_priority != priority->second.end());
385 if (has_priority->second) {
386 receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
388 if (priority->second.size() > 1)
389 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
391 priority->second.erase(mount_point);
392 if (priority->second.empty())
393 mount_priority_map_.erase(mount_device);
394 mount_points_to_erase.push_back(mount_point);
398 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
399 // using the iterator is slightly more efficient, but more tricky, since
400 // calling std::map::erase() on an iterator invalidates it.
401 for (std::list<base::FilePath>::const_iterator it =
402 mount_points_to_erase.begin();
403 it != mount_points_to_erase.end();
404 ++it) {
405 mount_info_map_.erase(*it);
408 // For any multiply mounted device where the mount that we had notified
409 // got detached, send a notification of attachment for one of the other
410 // mount points.
411 for (std::list<base::FilePath>::const_iterator it =
412 multiple_mounted_devices_needing_reattachment.begin();
413 it != multiple_mounted_devices_needing_reattachment.end();
414 ++it) {
415 ReferencedMountPoint::iterator first_mount_point_info =
416 mount_priority_map_.find(*it)->second.begin();
417 const base::FilePath& mount_point = first_mount_point_info->first;
418 first_mount_point_info->second = true;
420 const StorageInfo& mount_info =
421 mount_info_map_.find(mount_point)->second.storage_info;
422 DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
423 receiver()->ProcessAttach(mount_info);
426 // Check new mtab entries against existing ones.
427 for (MountPointDeviceMap::const_iterator new_iter = new_mtab.begin();
428 new_iter != new_mtab.end(); ++new_iter) {
429 const base::FilePath& mount_point = new_iter->first;
430 const base::FilePath& mount_device = new_iter->second;
431 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
432 if (old_iter == mount_info_map_.end() ||
433 old_iter->second.mount_device != mount_device) {
434 // New mount point found or an existing mount point found with a new
435 // device.
436 if (IsDeviceAlreadyMounted(mount_device)) {
437 HandleDeviceMountedMultipleTimes(mount_device, mount_point);
438 } else {
439 BrowserThread::PostTaskAndReplyWithResult(
440 BrowserThread::FILE, FROM_HERE,
441 base::Bind(get_device_info_callback_, mount_device, mount_point),
442 base::Bind(&StorageMonitorLinux::AddNewMount,
443 weak_ptr_factory_.GetWeakPtr(),
444 mount_device));
449 // Note: relies on scheduled tasks on the file thread being sequential. This
450 // block needs to follow the for loop, so that the DoNothing call on the FILE
451 // thread happens after the scheduled metadata retrievals, meaning that the
452 // reply callback will then happen after all the AddNewMount calls.
453 if (!IsInitialized()) {
454 BrowserThread::PostTaskAndReply(
455 BrowserThread::FILE, FROM_HERE,
456 base::Bind(&base::DoNothing),
457 base::Bind(&StorageMonitorLinux::MarkInitialized,
458 weak_ptr_factory_.GetWeakPtr()));
462 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
463 const base::FilePath& mount_device) const {
464 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
465 return ContainsKey(mount_priority_map_, mount_device);
468 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
469 const base::FilePath& mount_device,
470 const base::FilePath& mount_point) {
471 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
473 MountPriorityMap::iterator priority = mount_priority_map_.find(mount_device);
474 DCHECK(priority != mount_priority_map_.end());
475 const base::FilePath& other_mount_point = priority->second.begin()->first;
476 priority->second[mount_point] = false;
477 mount_info_map_[mount_point] =
478 mount_info_map_.find(other_mount_point)->second;
481 void StorageMonitorLinux::AddNewMount(const base::FilePath& mount_device,
482 scoped_ptr<StorageInfo> storage_info) {
483 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
485 if (!storage_info)
486 return;
488 DCHECK(!storage_info->device_id().empty());
490 bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
491 const base::FilePath mount_point(storage_info->location());
493 MountPointInfo mount_point_info;
494 mount_point_info.mount_device = mount_device;
495 mount_point_info.storage_info = *storage_info;
496 mount_info_map_[mount_point] = mount_point_info;
497 mount_priority_map_[mount_device][mount_point] = removable;
498 receiver()->ProcessAttach(*storage_info);
501 StorageMonitor* StorageMonitor::CreateInternal() {
502 const base::FilePath kDefaultMtabPath("/etc/mtab");
503 return new StorageMonitorLinux(kDefaultMtabPath);
506 } // namespace storage_monitor