Remove unused var from OffTheRecordProfileImplTest.GetHostZoomMap
[chromium-blink-merge.git] / components / storage_monitor / storage_monitor_linux.cc
blob254fd5d0de9b92d1a29fac61b9c7be609878738a
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"
29 #include "device/udev_linux/udev.h"
31 using content::BrowserThread;
33 namespace storage_monitor {
35 typedef MtabWatcherLinux::MountPointDeviceMap MountPointDeviceMap;
37 namespace {
39 // udev device property constants.
40 const char kBlockSubsystemKey[] = "block";
41 const char kDiskDeviceTypeKey[] = "disk";
42 const char kFsUUID[] = "ID_FS_UUID";
43 const char kLabel[] = "ID_FS_LABEL";
44 const char kModel[] = "ID_MODEL";
45 const char kModelID[] = "ID_MODEL_ID";
46 const char kRemovableSysAttr[] = "removable";
47 const char kSerialShort[] = "ID_SERIAL_SHORT";
48 const char kSizeSysAttr[] = "size";
49 const char kVendor[] = "ID_VENDOR";
50 const char kVendorID[] = "ID_VENDOR_ID";
52 // Construct a device id using label or manufacturer (vendor and model) details.
53 std::string MakeDeviceUniqueId(struct udev_device* device) {
54 std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
55 // Keep track of device uuid, to see how often we receive empty uuid values.
56 UMA_HISTOGRAM_BOOLEAN(
57 "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
58 !uuid.empty());
60 if (!uuid.empty())
61 return kFSUniqueIdPrefix + uuid;
63 // If one of the vendor, model, serial information is missing, its value
64 // in the string is empty.
65 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
66 // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
67 std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
68 std::string model = GetUdevDevicePropertyValue(device, kModelID);
69 std::string serial_short = GetUdevDevicePropertyValue(device,
70 kSerialShort);
71 if (vendor.empty() && model.empty() && serial_short.empty())
72 return std::string();
74 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
77 // Records GetDeviceInfo result on destruction, to see how often we fail to get
78 // device details.
79 class ScopedGetDeviceInfoResultRecorder {
80 public:
81 ScopedGetDeviceInfoResultRecorder() : result_(false) {}
82 ~ScopedGetDeviceInfoResultRecorder() {
83 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
84 result_);
87 void set_result(bool result) {
88 result_ = result;
91 private:
92 bool result_;
94 DISALLOW_COPY_AND_ASSIGN(ScopedGetDeviceInfoResultRecorder);
97 // Returns the storage partition size of the device specified by |device_path|.
98 // If the requested information is unavailable, returns 0.
99 uint64 GetDeviceStorageSize(const base::FilePath& device_path,
100 struct udev_device* device) {
101 // sysfs provides the device size in units of 512-byte blocks.
102 const std::string partition_size = udev_device_get_sysattr_value(
103 device, kSizeSysAttr);
105 // Keep track of device size, to see how often this information is
106 // unavailable.
107 UMA_HISTOGRAM_BOOLEAN(
108 "RemovableDeviceNotificationsLinux.device_partition_size_available",
109 !partition_size.empty());
111 uint64 total_size_in_bytes = 0;
112 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
113 return 0;
114 return (total_size_in_bytes <= kuint64max / 512) ?
115 total_size_in_bytes * 512 : 0;
118 // Gets the device information using udev library.
119 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
120 const base::FilePath& mount_point) {
121 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
122 DCHECK(!device_path.empty());
124 scoped_ptr<StorageInfo> storage_info;
126 ScopedGetDeviceInfoResultRecorder results_recorder;
128 device::ScopedUdevPtr udev_obj(udev_new());
129 if (!udev_obj.get())
130 return storage_info.Pass();
132 struct stat device_stat;
133 if (stat(device_path.value().c_str(), &device_stat) < 0)
134 return storage_info.Pass();
136 char device_type;
137 if (S_ISCHR(device_stat.st_mode))
138 device_type = 'c';
139 else if (S_ISBLK(device_stat.st_mode))
140 device_type = 'b';
141 else
142 return storage_info.Pass(); // Not a supported type.
144 device::ScopedUdevDevicePtr device(
145 udev_device_new_from_devnum(udev_obj.get(), device_type,
146 device_stat.st_rdev));
147 if (!device.get())
148 return storage_info.Pass();
150 base::string16 volume_label =
151 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kLabel));
152 base::string16 vendor_name =
153 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kVendor));
154 base::string16 model_name =
155 base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kModel));
157 std::string unique_id = MakeDeviceUniqueId(device.get());
159 // Keep track of device info details to see how often we get invalid values.
160 MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, volume_label);
162 const char* value =
163 udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
164 if (!value) {
165 // |parent_device| is owned by |device| and does not need to be cleaned
166 // up.
167 struct udev_device* parent_device =
168 udev_device_get_parent_with_subsystem_devtype(device.get(),
169 kBlockSubsystemKey,
170 kDiskDeviceTypeKey);
171 value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
173 const bool is_removable = (value && atoi(value) == 1);
175 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
176 if (is_removable) {
177 if (MediaStorageUtil::HasDcim(mount_point))
178 type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
179 else
180 type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
183 results_recorder.set_result(true);
185 storage_info.reset(new StorageInfo(
186 StorageInfo::MakeDeviceId(type, unique_id),
187 mount_point.value(),
188 volume_label,
189 vendor_name,
190 model_name,
191 GetDeviceStorageSize(device_path, device.get())));
192 return storage_info.Pass();
195 MtabWatcherLinux* CreateMtabWatcherLinuxOnFileThread(
196 const base::FilePath& mtab_path,
197 base::WeakPtr<MtabWatcherLinux::Delegate> delegate) {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
199 // Owned by caller.
200 return new MtabWatcherLinux(mtab_path, delegate);
203 StorageMonitor::EjectStatus EjectPathOnFileThread(
204 const base::FilePath& path,
205 const base::FilePath& device) {
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
208 // Note: Linux LSB says umount should exist in /bin.
209 static const char kUmountBinary[] = "/bin/umount";
210 std::vector<std::string> command;
211 command.push_back(kUmountBinary);
212 command.push_back(path.value());
214 base::LaunchOptions options;
215 base::ProcessHandle handle;
216 if (!base::LaunchProcess(command, options, &handle))
217 return StorageMonitor::EJECT_FAILURE;
219 int exit_code = -1;
220 if (!base::WaitForExitCodeWithTimeout(handle, &exit_code,
221 base::TimeDelta::FromMilliseconds(3000))) {
222 base::KillProcess(handle, -1, false);
223 base::EnsureProcessTerminated(handle);
224 return StorageMonitor::EJECT_FAILURE;
227 // TODO(gbillock): Make sure this is found in documentation
228 // somewhere. Experimentally it seems to hold that exit code
229 // 1 means device is in use.
230 if (exit_code == 1)
231 return StorageMonitor::EJECT_IN_USE;
232 if (exit_code != 0)
233 return StorageMonitor::EJECT_FAILURE;
235 return StorageMonitor::EJECT_OK;
238 } // namespace
240 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
241 : mtab_path_(path),
242 get_device_info_callback_(base::Bind(&GetDeviceInfo)),
243 weak_ptr_factory_(this) {
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
247 StorageMonitorLinux::~StorageMonitorLinux() {
248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251 void StorageMonitorLinux::Init() {
252 DCHECK(!mtab_path_.empty());
254 BrowserThread::PostTaskAndReplyWithResult(
255 BrowserThread::FILE, FROM_HERE,
256 base::Bind(&CreateMtabWatcherLinuxOnFileThread,
257 mtab_path_,
258 weak_ptr_factory_.GetWeakPtr()),
259 base::Bind(&StorageMonitorLinux::OnMtabWatcherCreated,
260 weak_ptr_factory_.GetWeakPtr()));
262 if (!media_transfer_protocol_manager_) {
263 scoped_refptr<base::MessageLoopProxy> loop_proxy =
264 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
265 media_transfer_protocol_manager_.reset(
266 device::MediaTransferProtocolManager::Initialize(loop_proxy));
269 media_transfer_protocol_device_observer_.reset(
270 new MediaTransferProtocolDeviceObserverLinux(
271 receiver(), media_transfer_protocol_manager_.get()));
274 bool StorageMonitorLinux::GetStorageInfoForPath(
275 const base::FilePath& path,
276 StorageInfo* device_info) const {
277 DCHECK(device_info);
278 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
280 // TODO(thestig) |media_transfer_protocol_device_observer_| should always be
281 // valid.
282 if (media_transfer_protocol_device_observer_ &&
283 media_transfer_protocol_device_observer_->GetStorageInfoForPath(
284 path, device_info)) {
285 return true;
288 if (!path.IsAbsolute())
289 return false;
291 base::FilePath current = path;
292 while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
293 current = current.DirName();
295 MountMap::const_iterator mount_info = mount_info_map_.find(current);
296 if (mount_info == mount_info_map_.end())
297 return false;
298 *device_info = mount_info->second.storage_info;
299 return true;
302 device::MediaTransferProtocolManager*
303 StorageMonitorLinux::media_transfer_protocol_manager() {
304 return media_transfer_protocol_manager_.get();
307 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
308 const GetDeviceInfoCallback& get_device_info_callback) {
309 get_device_info_callback_ = get_device_info_callback;
312 void StorageMonitorLinux::SetMediaTransferProtocolManagerForTest(
313 device::MediaTransferProtocolManager* test_manager) {
314 DCHECK(!media_transfer_protocol_manager_);
315 media_transfer_protocol_manager_.reset(test_manager);
318 void StorageMonitorLinux::EjectDevice(
319 const std::string& device_id,
320 base::Callback<void(EjectStatus)> callback) {
321 StorageInfo::Type type;
322 if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
323 callback.Run(EJECT_FAILURE);
324 return;
327 if (type == StorageInfo::MTP_OR_PTP) {
328 media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
329 return;
332 // Find the mount point for the given device ID.
333 base::FilePath path;
334 base::FilePath device;
335 for (MountMap::iterator mount_info = mount_info_map_.begin();
336 mount_info != mount_info_map_.end(); ++mount_info) {
337 if (mount_info->second.storage_info.device_id() == device_id) {
338 path = mount_info->first;
339 device = mount_info->second.mount_device;
340 mount_info_map_.erase(mount_info);
341 break;
345 if (path.empty()) {
346 callback.Run(EJECT_NO_SUCH_DEVICE);
347 return;
350 receiver()->ProcessDetach(device_id);
352 BrowserThread::PostTaskAndReplyWithResult(
353 BrowserThread::FILE, FROM_HERE,
354 base::Bind(&EjectPathOnFileThread, path, device),
355 callback);
358 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360 mtab_watcher_.reset(watcher);
363 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
364 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
366 // Check existing mtab entries for unaccounted mount points.
367 // These mount points must have been removed in the new mtab.
368 std::list<base::FilePath> mount_points_to_erase;
369 std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
370 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
371 old_iter != mount_info_map_.end(); ++old_iter) {
372 const base::FilePath& mount_point = old_iter->first;
373 const base::FilePath& mount_device = old_iter->second.mount_device;
374 MountPointDeviceMap::const_iterator new_iter = new_mtab.find(mount_point);
375 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
376 // |mount_point|.
377 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
378 MountPriorityMap::iterator priority =
379 mount_priority_map_.find(mount_device);
380 DCHECK(priority != mount_priority_map_.end());
381 ReferencedMountPoint::const_iterator has_priority =
382 priority->second.find(mount_point);
383 if (StorageInfo::IsRemovableDevice(
384 old_iter->second.storage_info.device_id())) {
385 DCHECK(has_priority != priority->second.end());
386 if (has_priority->second) {
387 receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
389 if (priority->second.size() > 1)
390 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
392 priority->second.erase(mount_point);
393 if (priority->second.empty())
394 mount_priority_map_.erase(mount_device);
395 mount_points_to_erase.push_back(mount_point);
399 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
400 // using the iterator is slightly more efficient, but more tricky, since
401 // calling std::map::erase() on an iterator invalidates it.
402 for (std::list<base::FilePath>::const_iterator it =
403 mount_points_to_erase.begin();
404 it != mount_points_to_erase.end();
405 ++it) {
406 mount_info_map_.erase(*it);
409 // For any multiply mounted device where the mount that we had notified
410 // got detached, send a notification of attachment for one of the other
411 // mount points.
412 for (std::list<base::FilePath>::const_iterator it =
413 multiple_mounted_devices_needing_reattachment.begin();
414 it != multiple_mounted_devices_needing_reattachment.end();
415 ++it) {
416 ReferencedMountPoint::iterator first_mount_point_info =
417 mount_priority_map_.find(*it)->second.begin();
418 const base::FilePath& mount_point = first_mount_point_info->first;
419 first_mount_point_info->second = true;
421 const StorageInfo& mount_info =
422 mount_info_map_.find(mount_point)->second.storage_info;
423 DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
424 receiver()->ProcessAttach(mount_info);
427 // Check new mtab entries against existing ones.
428 for (MountPointDeviceMap::const_iterator new_iter = new_mtab.begin();
429 new_iter != new_mtab.end(); ++new_iter) {
430 const base::FilePath& mount_point = new_iter->first;
431 const base::FilePath& mount_device = new_iter->second;
432 MountMap::iterator old_iter = mount_info_map_.find(mount_point);
433 if (old_iter == mount_info_map_.end() ||
434 old_iter->second.mount_device != mount_device) {
435 // New mount point found or an existing mount point found with a new
436 // device.
437 if (IsDeviceAlreadyMounted(mount_device)) {
438 HandleDeviceMountedMultipleTimes(mount_device, mount_point);
439 } else {
440 BrowserThread::PostTaskAndReplyWithResult(
441 BrowserThread::FILE, FROM_HERE,
442 base::Bind(get_device_info_callback_, mount_device, mount_point),
443 base::Bind(&StorageMonitorLinux::AddNewMount,
444 weak_ptr_factory_.GetWeakPtr(),
445 mount_device));
450 // Note: relies on scheduled tasks on the file thread being sequential. This
451 // block needs to follow the for loop, so that the DoNothing call on the FILE
452 // thread happens after the scheduled metadata retrievals, meaning that the
453 // reply callback will then happen after all the AddNewMount calls.
454 if (!IsInitialized()) {
455 BrowserThread::PostTaskAndReply(
456 BrowserThread::FILE, FROM_HERE,
457 base::Bind(&base::DoNothing),
458 base::Bind(&StorageMonitorLinux::MarkInitialized,
459 weak_ptr_factory_.GetWeakPtr()));
463 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
464 const base::FilePath& mount_device) const {
465 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
466 return ContainsKey(mount_priority_map_, mount_device);
469 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
470 const base::FilePath& mount_device,
471 const base::FilePath& mount_point) {
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
474 MountPriorityMap::iterator priority = mount_priority_map_.find(mount_device);
475 DCHECK(priority != mount_priority_map_.end());
476 const base::FilePath& other_mount_point = priority->second.begin()->first;
477 priority->second[mount_point] = false;
478 mount_info_map_[mount_point] =
479 mount_info_map_.find(other_mount_point)->second;
482 void StorageMonitorLinux::AddNewMount(const base::FilePath& mount_device,
483 scoped_ptr<StorageInfo> storage_info) {
484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
486 if (!storage_info)
487 return;
489 DCHECK(!storage_info->device_id().empty());
491 bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
492 const base::FilePath mount_point(storage_info->location());
494 MountPointInfo mount_point_info;
495 mount_point_info.mount_device = mount_device;
496 mount_point_info.storage_info = *storage_info;
497 mount_info_map_[mount_point] = mount_point_info;
498 mount_priority_map_[mount_device][mount_point] = removable;
499 receiver()->ProcessAttach(*storage_info);
502 StorageMonitor* StorageMonitor::CreateInternal() {
503 const base::FilePath kDefaultMtabPath("/etc/mtab");
504 return new StorageMonitorLinux(kDefaultMtabPath);
507 } // namespace storage_monitor