1 // Copyright 2014 The Chromium Authors
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"
20 #include "base/containers/contains.h"
21 #include "base/functional/bind.h"
22 #include "base/functional/callback_helpers.h"
23 #include "base/metrics/histogram_macros.h"
24 #include "base/process/kill.h"
25 #include "base/process/launch.h"
26 #include "base/process/process.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/utf_string_conversions.h"
30 #include "base/task/sequenced_task_runner.h"
31 #include "base/task/thread_pool.h"
32 #include "base/threading/scoped_blocking_call.h"
33 #include "components/storage_monitor/media_storage_util.h"
34 #include "components/storage_monitor/removable_device_constants.h"
35 #include "components/storage_monitor/storage_info.h"
36 #include "components/storage_monitor/udev_util_linux.h"
37 #include "device/udev_linux/scoped_udev.h"
39 namespace storage_monitor {
41 using MountPointDeviceMap = MtabWatcherLinux::MountPointDeviceMap;
45 // udev device property constants.
46 const char kBlockSubsystemKey[] = "block";
47 const char kDiskDeviceTypeKey[] = "disk";
48 const char kFsUUID[] = "ID_FS_UUID";
49 const char kLabel[] = "ID_FS_LABEL";
50 const char kModel[] = "ID_MODEL";
51 const char kModelID[] = "ID_MODEL_ID";
52 const char kRemovableSysAttr[] = "removable";
53 const char kSerialShort[] = "ID_SERIAL_SHORT";
54 const char kSizeSysAttr[] = "size";
55 const char kVendor[] = "ID_VENDOR";
56 const char kVendorID[] = "ID_VENDOR_ID";
58 // Construct a device id using label or manufacturer (vendor and model) details.
59 std::string MakeDeviceUniqueId(struct udev_device* device) {
60 std::string uuid = device::UdevDeviceGetPropertyValue(device, kFsUUID);
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())
75 return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
78 // Records GetDeviceInfo result on destruction, to see how often we fail to get
80 class ScopedGetDeviceInfoResultRecorder {
82 ScopedGetDeviceInfoResultRecorder() = default;
84 ScopedGetDeviceInfoResultRecorder(const ScopedGetDeviceInfoResultRecorder&) =
86 ScopedGetDeviceInfoResultRecorder& operator=(
87 const ScopedGetDeviceInfoResultRecorder&) = delete;
89 ~ScopedGetDeviceInfoResultRecorder() {
90 UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
94 void set_result(bool result) {
102 // Returns the storage partition size of the device specified by |device_path|.
103 // If the requested information is unavailable, returns 0.
104 uint64_t GetDeviceStorageSize(const base::FilePath& device_path,
105 struct udev_device* device) {
106 // sysfs provides the device size in units of 512-byte blocks.
107 const std::string partition_size =
108 device::UdevDeviceGetSysattrValue(device, kSizeSysAttr);
110 uint64_t total_size_in_bytes = 0;
111 if (!base::StringToUint64(partition_size, &total_size_in_bytes))
113 return (total_size_in_bytes <= std::numeric_limits<uint64_t>::max() / 512)
114 ? total_size_in_bytes * 512
118 // Gets the device information using udev library.
119 std::unique_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
120 const base::FilePath& mount_point) {
121 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
122 base::BlockingType::MAY_BLOCK);
123 DCHECK(!device_path.empty());
125 std::unique_ptr<StorageInfo> storage_info;
127 ScopedGetDeviceInfoResultRecorder results_recorder;
129 device::ScopedUdevPtr udev_obj(device::udev_new());
133 struct stat device_stat;
134 if (stat(device_path.value().c_str(), &device_stat) < 0)
138 if (S_ISCHR(device_stat.st_mode))
140 else if (S_ISBLK(device_stat.st_mode))
143 return storage_info; // 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));
151 std::u16string volume_label = base::UTF8ToUTF16(
152 device::UdevDeviceGetPropertyValue(device.get(), kLabel));
153 std::u16string vendor_name = base::UTF8ToUTF16(
154 device::UdevDeviceGetPropertyValue(device.get(), kVendor));
155 std::u16string model_name = base::UTF8ToUTF16(
156 device::UdevDeviceGetPropertyValue(device.get(), kModel));
158 std::string unique_id = MakeDeviceUniqueId(device.get());
160 device::udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
162 // |parent_device| is owned by |device| and does not need to be cleaned
164 struct udev_device* parent_device =
165 device::udev_device_get_parent_with_subsystem_devtype(
169 value = device::udev_device_get_sysattr_value(parent_device,
172 const bool is_removable = (value && atoi(value) == 1);
174 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
176 type = MediaStorageUtil::HasDcim(mount_point)
177 ? StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM
178 : StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
181 results_recorder.set_result(true);
183 storage_info = std::make_unique<StorageInfo>(
184 StorageInfo::MakeDeviceId(type, unique_id), mount_point.value(),
185 volume_label, vendor_name, model_name,
186 GetDeviceStorageSize(device_path, device.get()));
190 // Runs |callback| with the |new_mtab| on |storage_monitor_task_runner|.
191 void BounceMtabUpdateToStorageMonitorTaskRunner(
192 scoped_refptr<base::SequencedTaskRunner> storage_monitor_task_runner,
193 const MtabWatcherLinux::UpdateMtabCallback& callback,
194 const MtabWatcherLinux::MountPointDeviceMap& new_mtab) {
195 storage_monitor_task_runner->PostTask(FROM_HERE,
196 base::BindOnce(callback, new_mtab));
199 std::unique_ptr<MtabWatcherLinux> CreateMtabWatcherLinuxOnMtabWatcherTaskRunner(
200 const base::FilePath& mtab_path,
201 scoped_refptr<base::SequencedTaskRunner> storage_monitor_task_runner,
202 const MtabWatcherLinux::UpdateMtabCallback& callback) {
203 return std::make_unique<MtabWatcherLinux>(
205 base::BindRepeating(&BounceMtabUpdateToStorageMonitorTaskRunner,
206 storage_monitor_task_runner, callback));
209 StorageMonitor::EjectStatus EjectPathOnBlockingTaskRunner(
210 const base::FilePath& path,
211 const base::FilePath& device) {
212 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
213 base::BlockingType::MAY_BLOCK);
215 // Note: Linux LSB says umount should exist in /bin.
216 static const char kUmountBinary[] = "/bin/umount";
217 std::vector<std::string> command{kUmountBinary, path.value()};
219 base::LaunchOptions options;
220 base::Process process = base::LaunchProcess(command, options);
221 if (!process.IsValid())
222 return StorageMonitor::EJECT_FAILURE;
224 static constexpr auto kEjectTimeout = base::Seconds(3);
226 if (!process.WaitForExitWithTimeout(kEjectTimeout, &exit_code)) {
227 process.Terminate(-1, false);
228 base::EnsureProcessTerminated(std::move(process));
229 return StorageMonitor::EJECT_FAILURE;
232 // TODO(gbillock): Make sure this is found in documentation
233 // somewhere. Experimentally it seems to hold that exit code
234 // 1 means device is in use.
236 return StorageMonitor::EJECT_IN_USE;
238 return StorageMonitor::EJECT_FAILURE;
240 return StorageMonitor::EJECT_OK;
245 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
247 get_device_info_callback_(base::BindRepeating(&GetDeviceInfo)),
248 mtab_watcher_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
249 {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {}
251 StorageMonitorLinux::~StorageMonitorLinux() {
252 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253 mtab_watcher_task_runner_->DeleteSoon(FROM_HERE, mtab_watcher_.release());
256 void StorageMonitorLinux::Init() {
257 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
258 DCHECK(!mtab_path_.empty());
260 mtab_watcher_task_runner_->PostTaskAndReplyWithResult(
262 base::BindOnce(&CreateMtabWatcherLinuxOnMtabWatcherTaskRunner, mtab_path_,
263 base::SequencedTaskRunner::GetCurrentDefault(),
264 base::BindRepeating(&StorageMonitorLinux::UpdateMtab,
265 weak_ptr_factory_.GetWeakPtr())),
266 base::BindOnce(&StorageMonitorLinux::OnMtabWatcherCreated,
267 weak_ptr_factory_.GetWeakPtr()));
270 bool StorageMonitorLinux::GetStorageInfoForPath(
271 const base::FilePath& path,
272 StorageInfo* device_info) const {
273 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
276 if (!path.IsAbsolute())
279 base::FilePath current = path;
280 while (!base::Contains(mount_info_map_, current) &&
281 current != current.DirName())
282 current = current.DirName();
284 auto mount_info = mount_info_map_.find(current);
285 if (mount_info == mount_info_map_.end())
287 *device_info = mount_info->second.storage_info;
291 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
292 const GetDeviceInfoCallback& get_device_info_callback) {
293 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
294 get_device_info_callback_ = get_device_info_callback;
297 void StorageMonitorLinux::EjectDevice(
298 const std::string& device_id,
299 base::OnceCallback<void(EjectStatus)> callback) {
300 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301 StorageInfo::Type type;
302 if (!StorageInfo::CrackDeviceId(device_id, &type, nullptr)) {
303 std::move(callback).Run(EJECT_FAILURE);
307 DCHECK_NE(type, StorageInfo::MTP_OR_PTP);
309 // Find the mount point for the given device ID.
311 base::FilePath device;
312 for (auto mount_info = mount_info_map_.begin();
313 mount_info != mount_info_map_.end(); ++mount_info) {
314 if (mount_info->second.storage_info.device_id() == device_id) {
315 path = mount_info->first;
316 device = mount_info->second.mount_device;
317 mount_info_map_.erase(mount_info);
323 std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
327 receiver()->ProcessDetach(device_id);
329 base::ThreadPool::PostTaskAndReplyWithResult(
330 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
331 base::BindOnce(&EjectPathOnBlockingTaskRunner, path, device),
332 std::move(callback));
335 void StorageMonitorLinux::OnMtabWatcherCreated(
336 std::unique_ptr<MtabWatcherLinux> watcher) {
337 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
338 mtab_watcher_ = std::move(watcher);
341 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
342 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
343 // Check existing mtab entries for unaccounted mount points.
344 // These mount points must have been removed in the new mtab.
345 std::list<base::FilePath> mount_points_to_erase;
346 std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
347 for (MountMap::const_iterator old_iter = mount_info_map_.begin();
348 old_iter != mount_info_map_.end(); ++old_iter) {
349 const base::FilePath& mount_point = old_iter->first;
350 const base::FilePath& mount_device = old_iter->second.mount_device;
351 auto new_iter = new_mtab.find(mount_point);
352 // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
354 if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
355 auto priority = mount_priority_map_.find(mount_device);
356 DCHECK(priority != mount_priority_map_.end());
357 ReferencedMountPoint::const_iterator has_priority =
358 priority->second.find(mount_point);
359 if (StorageInfo::IsRemovableDevice(
360 old_iter->second.storage_info.device_id())) {
361 DCHECK(has_priority != priority->second.end());
362 if (has_priority->second) {
363 receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
365 if (priority->second.size() > 1)
366 multiple_mounted_devices_needing_reattachment.push_back(mount_device);
368 priority->second.erase(mount_point);
369 if (priority->second.empty())
370 mount_priority_map_.erase(mount_device);
371 mount_points_to_erase.push_back(mount_point);
375 // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
376 // using the iterator is slightly more efficient, but more tricky, since
377 // calling std::map::erase() on an iterator invalidates it.
378 for (std::list<base::FilePath>::const_iterator it =
379 mount_points_to_erase.begin();
380 it != mount_points_to_erase.end();
382 mount_info_map_.erase(*it);
385 // For any multiply mounted device where the mount that we had notified
386 // got detached, send a notification of attachment for one of the other
388 for (std::list<base::FilePath>::const_iterator it =
389 multiple_mounted_devices_needing_reattachment.begin();
390 it != multiple_mounted_devices_needing_reattachment.end();
392 auto first_mount_point_info = mount_priority_map_.find(*it)->second.begin();
393 const base::FilePath& mount_point = first_mount_point_info->first;
394 first_mount_point_info->second = true;
396 const StorageInfo& mount_info =
397 mount_info_map_.find(mount_point)->second.storage_info;
398 DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
399 receiver()->ProcessAttach(mount_info);
402 // Check new mtab entries against existing ones.
403 scoped_refptr<base::SequencedTaskRunner> mounting_task_runner =
404 base::ThreadPool::CreateSequencedTaskRunner(
405 {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
406 for (auto new_iter = new_mtab.begin(); new_iter != new_mtab.end();
408 const base::FilePath& mount_point = new_iter->first;
409 const base::FilePath& mount_device = new_iter->second;
410 auto old_iter = mount_info_map_.find(mount_point);
411 if (old_iter == mount_info_map_.end() ||
412 old_iter->second.mount_device != mount_device) {
413 // New mount point found or an existing mount point found with a new
415 if (IsDeviceAlreadyMounted(mount_device)) {
416 HandleDeviceMountedMultipleTimes(mount_device, mount_point);
418 mounting_task_runner->PostTaskAndReplyWithResult(
420 base::BindOnce(get_device_info_callback_, mount_device,
422 base::BindOnce(&StorageMonitorLinux::AddNewMount,
423 weak_ptr_factory_.GetWeakPtr(), mount_device));
428 // Note: Relies on scheduled tasks on the |mounting_task_runner| being
429 // sequential. This block needs to follow the for loop, so that the DoNothing
430 // call on the |mounting_task_runner| happens after the scheduled metadata
431 // retrievals, meaning that the reply callback will then happen after all the
432 // AddNewMount calls.
433 if (!IsInitialized()) {
434 mounting_task_runner->PostTaskAndReply(
435 FROM_HERE, base::DoNothing(),
436 base::BindOnce(&StorageMonitorLinux::MarkInitialized,
437 weak_ptr_factory_.GetWeakPtr()));
441 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
442 const base::FilePath& mount_device) const {
443 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
444 return base::Contains(mount_priority_map_, mount_device);
447 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
448 const base::FilePath& mount_device,
449 const base::FilePath& mount_point) {
450 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
452 auto priority = mount_priority_map_.find(mount_device);
453 DCHECK(priority != mount_priority_map_.end());
454 const base::FilePath& other_mount_point = priority->second.begin()->first;
455 priority->second[mount_point] = false;
456 mount_info_map_[mount_point] =
457 mount_info_map_.find(other_mount_point)->second;
460 void StorageMonitorLinux::AddNewMount(
461 const base::FilePath& mount_device,
462 std::unique_ptr<StorageInfo> storage_info) {
463 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
468 DCHECK(!storage_info->device_id().empty());
470 bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
471 const base::FilePath mount_point(storage_info->location());
473 MountPointInfo mount_point_info;
474 mount_point_info.mount_device = mount_device;
475 mount_point_info.storage_info = *storage_info;
476 mount_info_map_[mount_point] = mount_point_info;
477 mount_priority_map_[mount_device][mount_point] = removable;
478 receiver()->ProcessAttach(*storage_info);
481 StorageMonitor* StorageMonitor::CreateInternal() {
482 const base::FilePath kDefaultMtabPath("/etc/mtab");
483 return new StorageMonitorLinux(kDefaultMtabPath);
486 } // namespace storage_monitor