Add an exponential backoff to rechecking the app list doodle.
[chromium-blink-merge.git] / components / storage_monitor / storage_monitor_linux.cc
blob436304980d72be3af4ae3dde25cf7373ea59e04c
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/process/process.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "components/storage_monitor/media_storage_util.h"
25 #include "components/storage_monitor/media_transfer_protocol_device_observer_linux.h"
26 #include "components/storage_monitor/removable_device_constants.h"
27 #include "components/storage_monitor/storage_info.h"
28 #include "components/storage_monitor/udev_util_linux.h"
29 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
30 #include "device/udev_linux/scoped_udev.h"
32 using content::BrowserThread;
34 namespace storage_monitor {
36 typedef MtabWatcherLinux::MountPointDeviceMap MountPointDeviceMap;
38 namespace {
40 // udev device property constants.
41 const char kBlockSubsystemKey[] = "block";
42 const char kDiskDeviceTypeKey[] = "disk";
43 const char kFsUUID[] = "ID_FS_UUID";
44 const char kLabel[] = "ID_FS_LABEL";
45 const char kModel[] = "ID_MODEL";
46 const char kModelID[] = "ID_MODEL_ID";
47 const char kRemovableSysAttr[] = "removable";
48 const char kSerialShort[] = "ID_SERIAL_SHORT";
49 const char kSizeSysAttr[] = "size";
50 const char kVendor[] = "ID_VENDOR";
51 const char kVendorID[] = "ID_VENDOR_ID";
53 // Construct a device id using label or manufacturer (vendor and model) details.
54 std::string MakeDeviceUniqueId(struct udev_device* device) {
55 std::string uuid = device::UdevDeviceGetPropertyValue(device, kFsUUID);
56 // Keep track of device uuid, to see how often we receive empty uuid values.
57 UMA_HISTOGRAM_BOOLEAN(
58 "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
59 !uuid.empty());
61 if (!uuid.empty())
62 return kFSUniqueIdPrefix + uuid;
64 // If one of the vendor, model, serial information is missing, its value
65 // in the string is empty.
66 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
67 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
68 std::string vendor = device::UdevDeviceGetPropertyValue(device, kVendorID);
69 std::string model = device::UdevDeviceGetPropertyValue(device, kModelID);
70 std::string serial_short =
71 device::UdevDeviceGetPropertyValue(device, kSerialShort);
72 if (vendor.empty() && model.empty() && serial_short.empty())
73 return std::string();
75 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
78 // Records GetDeviceInfo result on destruction, to see how often we fail to get
79 // device details.
80 class ScopedGetDeviceInfoResultRecorder {
81 public:
82 ScopedGetDeviceInfoResultRecorder() : result_(false) {}
83 ~ScopedGetDeviceInfoResultRecorder() {
84 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
85 result_);
88 void set_result(bool result) {
89 result_ = result;
92 private:
93 bool result_;
95 DISALLOW_COPY_AND_ASSIGN(ScopedGetDeviceInfoResultRecorder);
98 // Returns the storage partition size of the device specified by |device_path|.
99 // If the requested information is unavailable, returns 0.
100 uint64 GetDeviceStorageSize(const base::FilePath& device_path,
101 struct udev_device* device) {
102 // sysfs provides the device size in units of 512-byte blocks.
103 const std::string partition_size =
104 device::UdevDeviceGetSysattrValue(device, kSizeSysAttr);
106 // Keep track of device size, to see how often this information is
107 // unavailable.
108 UMA_HISTOGRAM_BOOLEAN(
109 "RemovableDeviceNotificationsLinux.device_partition_size_available",
110 !partition_size.empty());
112 uint64 total_size_in_bytes = 0;
113 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
114 return 0;
115 return (total_size_in_bytes <= kuint64max / 512) ?
116 total_size_in_bytes * 512 : 0;
119 // Gets the device information using udev library.
120 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
121 const base::FilePath& mount_point) {
122 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
123 DCHECK(!device_path.empty());
125 scoped_ptr<StorageInfo> storage_info;
127 ScopedGetDeviceInfoResultRecorder results_recorder;
129 device::ScopedUdevPtr udev_obj(device::udev_new());
130 if (!udev_obj.get())
131 return storage_info.Pass();
133 struct stat device_stat;
134 if (stat(device_path.value().c_str(), &device_stat) < 0)
135 return storage_info.Pass();
137 char device_type;
138 if (S_ISCHR(device_stat.st_mode))
139 device_type = 'c';
140 else if (S_ISBLK(device_stat.st_mode))
141 device_type = 'b';
142 else
143 return storage_info.Pass(); // Not a supported type.
145 device::ScopedUdevDevicePtr device(
146 device::udev_device_new_from_devnum(udev_obj.get(), device_type,
147 device_stat.st_rdev));
148 if (!device.get())
149 return storage_info.Pass();
151 base::string16 volume_label = base::UTF8ToUTF16(
152 device::UdevDeviceGetPropertyValue(device.get(), kLabel));
153 base::string16 vendor_name = base::UTF8ToUTF16(
154 device::UdevDeviceGetPropertyValue(device.get(), kVendor));
155 base::string16 model_name = base::UTF8ToUTF16(
156 device::UdevDeviceGetPropertyValue(device.get(), kModel));
158 std::string unique_id = MakeDeviceUniqueId(device.get());
160 // Keep track of device info details to see how often we get invalid values.
161 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, volume_label);
163 const char* value =
164 device::udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
165 if (!value) {
166 // |parent_device| is owned by |device| and does not need to be cleaned
167 // up.
168 struct udev_device* parent_device =
169 device::udev_device_get_parent_with_subsystem_devtype(
170 device.get(),
171 kBlockSubsystemKey,
172 kDiskDeviceTypeKey);
173 value = device::udev_device_get_sysattr_value(parent_device,
174 kRemovableSysAttr);
176 const bool is_removable = (value && atoi(value) == 1);
178 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
179 if (is_removable) {
180 if (MediaStorageUtil::HasDcim(mount_point))
181 type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
182 else
183 type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
186 results_recorder.set_result(true);
188 storage_info.reset(new StorageInfo(
189 StorageInfo::MakeDeviceId(type, unique_id),
190 mount_point.value(),
191 volume_label,
192 vendor_name,
193 model_name,
194 GetDeviceStorageSize(device_path, device.get())));
195 return storage_info.Pass();
198 MtabWatcherLinux* CreateMtabWatcherLinuxOnFileThread(
199 const base::FilePath& mtab_path,
200 base::WeakPtr<MtabWatcherLinux::Delegate> delegate) {
201 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
202 // Owned by caller.
203 return new MtabWatcherLinux(mtab_path, delegate);
206 StorageMonitor::EjectStatus EjectPathOnFileThread(
207 const base::FilePath& path,
208 const base::FilePath& device) {
209 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
211 // Note: Linux LSB says umount should exist in /bin.
212 static const char kUmountBinary[] = "/bin/umount";
213 std::vector<std::string> command;
214 command.push_back(kUmountBinary);
215 command.push_back(path.value());
217 base::LaunchOptions options;
218 base::Process process = base::LaunchProcess(command, options);
219 if (!process.IsValid())
220 return StorageMonitor::EJECT_FAILURE;
222 int exit_code = -1;
223 if (!process.WaitForExitWithTimeout(base::TimeDelta::FromMilliseconds(3000),
224 &exit_code)) {
225 process.Terminate(-1, false);
226 base::EnsureProcessTerminated(process.Pass());
227 return StorageMonitor::EJECT_FAILURE;
230 // TODO(gbillock): Make sure this is found in documentation
231 // somewhere. Experimentally it seems to hold that exit code
232 // 1 means device is in use.
233 if (exit_code == 1)
234 return StorageMonitor::EJECT_IN_USE;
235 if (exit_code != 0)
236 return StorageMonitor::EJECT_FAILURE;
238 return StorageMonitor::EJECT_OK;
241 } // namespace
243 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
244 : mtab_path_(path),
245 get_device_info_callback_(base::Bind(&GetDeviceInfo)),
246 weak_ptr_factory_(this) {
247 DCHECK_CURRENTLY_ON(BrowserThread::UI);
250 StorageMonitorLinux::~StorageMonitorLinux() {
251 DCHECK_CURRENTLY_ON(BrowserThread::UI);
254 void StorageMonitorLinux::Init() {
255 DCHECK(!mtab_path_.empty());
257 BrowserThread::PostTaskAndReplyWithResult(
258 BrowserThread::FILE, FROM_HERE,
259 base::Bind(&CreateMtabWatcherLinuxOnFileThread,
260 mtab_path_,
261 weak_ptr_factory_.GetWeakPtr()),
262 base::Bind(&StorageMonitorLinux::OnMtabWatcherCreated,
263 weak_ptr_factory_.GetWeakPtr()));
265 if (!media_transfer_protocol_manager_) {
266 scoped_refptr<base::MessageLoopProxy> loop_proxy =
267 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
268 media_transfer_protocol_manager_.reset(
269 device::MediaTransferProtocolManager::Initialize(loop_proxy));
272 media_transfer_protocol_device_observer_.reset(
273 new MediaTransferProtocolDeviceObserverLinux(
274 receiver(), media_transfer_protocol_manager_.get()));
277 bool StorageMonitorLinux::GetStorageInfoForPath(
278 const base::FilePath& path,
279 StorageInfo* device_info) const {
280 DCHECK(device_info);
281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
283 // TODO(thestig) |media_transfer_protocol_device_observer_| should always be
284 // valid.
285 if (media_transfer_protocol_device_observer_ &&
286 media_transfer_protocol_device_observer_->GetStorageInfoForPath(
287 path, device_info)) {
288 return true;
291 if (!path.IsAbsolute())
292 return false;
294 base::FilePath current = path;
295 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
296 current = current.DirName();
298 MountMap::const_iterator mount_info = mount_info_map_.find(current);
299 if (mount_info == mount_info_map_.end())
300 return false;
301 *device_info = mount_info->second.storage_info;
302 return true;
305 device::MediaTransferProtocolManager*
306 StorageMonitorLinux::media_transfer_protocol_manager() {
307 return media_transfer_protocol_manager_.get();
310 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
311 const GetDeviceInfoCallback& get_device_info_callback) {
312 get_device_info_callback_ = get_device_info_callback;
315 void StorageMonitorLinux::SetMediaTransferProtocolManagerForTest(
316 device::MediaTransferProtocolManager* test_manager) {
317 DCHECK(!media_transfer_protocol_manager_);
318 media_transfer_protocol_manager_.reset(test_manager);
321 void StorageMonitorLinux::EjectDevice(
322 const std::string& device_id,
323 base::Callback<void(EjectStatus)> callback) {
324 StorageInfo::Type type;
325 if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
326 callback.Run(EJECT_FAILURE);
327 return;
330 if (type == StorageInfo::MTP_OR_PTP) {
331 media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
332 return;
335 // Find the mount point for the given device ID.
336 base::FilePath path;
337 base::FilePath device;
338 for (MountMap::iterator mount_info = mount_info_map_.begin();
339 mount_info != mount_info_map_.end(); ++mount_info) {
340 if (mount_info->second.storage_info.device_id() == device_id) {
341 path = mount_info->first;
342 device = mount_info->second.mount_device;
343 mount_info_map_.erase(mount_info);
344 break;
348 if (path.empty()) {
349 callback.Run(EJECT_NO_SUCH_DEVICE);
350 return;
353 receiver()->ProcessDetach(device_id);
355 BrowserThread::PostTaskAndReplyWithResult(
356 BrowserThread::FILE, FROM_HERE,
357 base::Bind(&EjectPathOnFileThread, path, device),
358 callback);
361 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
362 DCHECK_CURRENTLY_ON(BrowserThread::UI);
363 mtab_watcher_.reset(watcher);
366 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
367 DCHECK_CURRENTLY_ON(BrowserThread::UI);
369 // Check existing mtab entries for unaccounted mount points.
370 // These mount points must have been removed in the new mtab.
371 std::list<base::FilePath> mount_points_to_erase;
372 std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
373 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
374 old_iter != mount_info_map_.end(); ++old_iter) {
375 const base::FilePath& mount_point = old_iter->first;
376 const base::FilePath& mount_device = old_iter->second.mount_device;
377 MountPointDeviceMap::const_iterator new_iter = new_mtab.find(mount_point);
378 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
379 // |mount_point|.
380 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
381 MountPriorityMap::iterator priority =
382 mount_priority_map_.find(mount_device);
383 DCHECK(priority != mount_priority_map_.end());
384 ReferencedMountPoint::const_iterator has_priority =
385 priority->second.find(mount_point);
386 if (StorageInfo::IsRemovableDevice(
387 old_iter->second.storage_info.device_id())) {
388 DCHECK(has_priority != priority->second.end());
389 if (has_priority->second) {
390 receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
392 if (priority->second.size() > 1)
393 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
395 priority->second.erase(mount_point);
396 if (priority->second.empty())
397 mount_priority_map_.erase(mount_device);
398 mount_points_to_erase.push_back(mount_point);
402 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
403 // using the iterator is slightly more efficient, but more tricky, since
404 // calling std::map::erase() on an iterator invalidates it.
405 for (std::list<base::FilePath>::const_iterator it =
406 mount_points_to_erase.begin();
407 it != mount_points_to_erase.end();
408 ++it) {
409 mount_info_map_.erase(*it);
412 // For any multiply mounted device where the mount that we had notified
413 // got detached, send a notification of attachment for one of the other
414 // mount points.
415 for (std::list<base::FilePath>::const_iterator it =
416 multiple_mounted_devices_needing_reattachment.begin();
417 it != multiple_mounted_devices_needing_reattachment.end();
418 ++it) {
419 ReferencedMountPoint::iterator first_mount_point_info =
420 mount_priority_map_.find(*it)->second.begin();
421 const base::FilePath& mount_point = first_mount_point_info->first;
422 first_mount_point_info->second = true;
424 const StorageInfo& mount_info =
425 mount_info_map_.find(mount_point)->second.storage_info;
426 DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
427 receiver()->ProcessAttach(mount_info);
430 // Check new mtab entries against existing ones.
431 for (MountPointDeviceMap::const_iterator new_iter = new_mtab.begin();
432 new_iter != new_mtab.end(); ++new_iter) {
433 const base::FilePath& mount_point = new_iter->first;
434 const base::FilePath& mount_device = new_iter->second;
435 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
436 if (old_iter == mount_info_map_.end() ||
437 old_iter->second.mount_device != mount_device) {
438 // New mount point found or an existing mount point found with a new
439 // device.
440 if (IsDeviceAlreadyMounted(mount_device)) {
441 HandleDeviceMountedMultipleTimes(mount_device, mount_point);
442 } else {
443 BrowserThread::PostTaskAndReplyWithResult(
444 BrowserThread::FILE, FROM_HERE,
445 base::Bind(get_device_info_callback_, mount_device, mount_point),
446 base::Bind(&StorageMonitorLinux::AddNewMount,
447 weak_ptr_factory_.GetWeakPtr(),
448 mount_device));
453 // Note: relies on scheduled tasks on the file thread being sequential. This
454 // block needs to follow the for loop, so that the DoNothing call on the FILE
455 // thread happens after the scheduled metadata retrievals, meaning that the
456 // reply callback will then happen after all the AddNewMount calls.
457 if (!IsInitialized()) {
458 BrowserThread::PostTaskAndReply(
459 BrowserThread::FILE, FROM_HERE,
460 base::Bind(&base::DoNothing),
461 base::Bind(&StorageMonitorLinux::MarkInitialized,
462 weak_ptr_factory_.GetWeakPtr()));
466 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
467 const base::FilePath& mount_device) const {
468 DCHECK_CURRENTLY_ON(BrowserThread::UI);
469 return ContainsKey(mount_priority_map_, mount_device);
472 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
473 const base::FilePath& mount_device,
474 const base::FilePath& mount_point) {
475 DCHECK_CURRENTLY_ON(BrowserThread::UI);
477 MountPriorityMap::iterator priority = mount_priority_map_.find(mount_device);
478 DCHECK(priority != mount_priority_map_.end());
479 const base::FilePath& other_mount_point = priority->second.begin()->first;
480 priority->second[mount_point] = false;
481 mount_info_map_[mount_point] =
482 mount_info_map_.find(other_mount_point)->second;
485 void StorageMonitorLinux::AddNewMount(const base::FilePath& mount_device,
486 scoped_ptr<StorageInfo> storage_info) {
487 DCHECK_CURRENTLY_ON(BrowserThread::UI);
489 if (!storage_info)
490 return;
492 DCHECK(!storage_info->device_id().empty());
494 bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
495 const base::FilePath mount_point(storage_info->location());
497 MountPointInfo mount_point_info;
498 mount_point_info.mount_device = mount_device;
499 mount_point_info.storage_info = *storage_info;
500 mount_info_map_[mount_point] = mount_point_info;
501 mount_priority_map_[mount_device][mount_point] = removable;
502 receiver()->ProcessAttach(*storage_info);
505 StorageMonitor* StorageMonitor::CreateInternal() {
506 const base::FilePath kDefaultMtabPath("/etc/mtab");
507 return new StorageMonitorLinux(kDefaultMtabPath);
510 } // namespace storage_monitor