[M120 Migration][VD] Enable direct rendering for TVPlus
[platform/framework/web/chromium-efl.git] / components / storage_monitor / storage_monitor_linux.cc
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.
4
5 // StorageMonitorLinux implementation.
6
7 #include "components/storage_monitor/storage_monitor_linux.h"
8
9 #include <mntent.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <sys/stat.h>
13
14 #include <limits>
15 #include <list>
16 #include <memory>
17 #include <utility>
18 #include <vector>
19
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"
38
39 namespace storage_monitor {
40
41 using MountPointDeviceMap = MtabWatcherLinux::MountPointDeviceMap;
42
43 namespace {
44
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";
57
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);
61   if (!uuid.empty())
62     return kFSUniqueIdPrefix + uuid;
63
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();
74
75   return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
76 }
77
78 // Records GetDeviceInfo result on destruction, to see how often we fail to get
79 // device details.
80 class ScopedGetDeviceInfoResultRecorder {
81  public:
82   ScopedGetDeviceInfoResultRecorder() = default;
83
84   ScopedGetDeviceInfoResultRecorder(const ScopedGetDeviceInfoResultRecorder&) =
85       delete;
86   ScopedGetDeviceInfoResultRecorder& operator=(
87       const ScopedGetDeviceInfoResultRecorder&) = delete;
88
89   ~ScopedGetDeviceInfoResultRecorder() {
90     UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
91                           result_);
92   }
93
94   void set_result(bool result) {
95     result_ = result;
96   }
97
98  private:
99   bool result_ = false;
100 };
101
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);
109
110   uint64_t total_size_in_bytes = 0;
111   if (!base::StringToUint64(partition_size, &total_size_in_bytes))
112     return 0;
113   return (total_size_in_bytes <= std::numeric_limits<uint64_t>::max() / 512)
114              ? total_size_in_bytes * 512
115              : 0;
116 }
117
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());
124
125   std::unique_ptr<StorageInfo> storage_info;
126
127   ScopedGetDeviceInfoResultRecorder results_recorder;
128
129   device::ScopedUdevPtr udev_obj(device::udev_new());
130   if (!udev_obj.get())
131     return storage_info;
132
133   struct stat device_stat;
134   if (stat(device_path.value().c_str(), &device_stat) < 0)
135     return storage_info;
136
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;  // Not a supported type.
144
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;
150
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));
157
158   std::string unique_id = MakeDeviceUniqueId(device.get());
159   const char* value =
160       device::udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
161   if (!value) {
162     // |parent_device| is owned by |device| and does not need to be cleaned
163     // up.
164     struct udev_device* parent_device =
165         device::udev_device_get_parent_with_subsystem_devtype(
166             device.get(),
167             kBlockSubsystemKey,
168             kDiskDeviceTypeKey);
169     value = device::udev_device_get_sysattr_value(parent_device,
170                                                   kRemovableSysAttr);
171   }
172   const bool is_removable = (value && atoi(value) == 1);
173
174   StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
175   if (is_removable) {
176     type = MediaStorageUtil::HasDcim(mount_point)
177                ? StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM
178                : StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
179   }
180
181   results_recorder.set_result(true);
182
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()));
187   return storage_info;
188 }
189
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));
197 }
198
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>(
204       mtab_path,
205       base::BindRepeating(&BounceMtabUpdateToStorageMonitorTaskRunner,
206                           storage_monitor_task_runner, callback));
207 }
208
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);
214
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()};
218
219   base::LaunchOptions options;
220   base::Process process = base::LaunchProcess(command, options);
221   if (!process.IsValid())
222     return StorageMonitor::EJECT_FAILURE;
223
224   static constexpr auto kEjectTimeout = base::Seconds(3);
225   int exit_code = -1;
226   if (!process.WaitForExitWithTimeout(kEjectTimeout, &exit_code)) {
227     process.Terminate(-1, false);
228     base::EnsureProcessTerminated(std::move(process));
229     return StorageMonitor::EJECT_FAILURE;
230   }
231
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.
235   if (exit_code == 1)
236     return StorageMonitor::EJECT_IN_USE;
237   if (exit_code != 0)
238     return StorageMonitor::EJECT_FAILURE;
239
240   return StorageMonitor::EJECT_OK;
241 }
242
243 }  // namespace
244
245 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
246     : mtab_path_(path),
247       get_device_info_callback_(base::BindRepeating(&GetDeviceInfo)),
248       mtab_watcher_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
249           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {}
250
251 StorageMonitorLinux::~StorageMonitorLinux() {
252   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253   mtab_watcher_task_runner_->DeleteSoon(FROM_HERE, mtab_watcher_.release());
254 }
255
256 void StorageMonitorLinux::Init() {
257   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
258   DCHECK(!mtab_path_.empty());
259
260   mtab_watcher_task_runner_->PostTaskAndReplyWithResult(
261       FROM_HERE,
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()));
268 }
269
270 bool StorageMonitorLinux::GetStorageInfoForPath(
271     const base::FilePath& path,
272     StorageInfo* device_info) const {
273   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
274   DCHECK(device_info);
275
276   if (!path.IsAbsolute())
277     return false;
278
279   base::FilePath current = path;
280   while (!base::Contains(mount_info_map_, current) &&
281          current != current.DirName())
282     current = current.DirName();
283
284   auto mount_info = mount_info_map_.find(current);
285   if (mount_info == mount_info_map_.end())
286     return false;
287   *device_info = mount_info->second.storage_info;
288   return true;
289 }
290
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;
295 }
296
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);
304     return;
305   }
306
307   DCHECK_NE(type, StorageInfo::MTP_OR_PTP);
308
309   // Find the mount point for the given device ID.
310   base::FilePath path;
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);
318       break;
319     }
320   }
321
322   if (path.empty()) {
323     std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
324     return;
325   }
326
327   receiver()->ProcessDetach(device_id);
328
329   base::ThreadPool::PostTaskAndReplyWithResult(
330       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
331       base::BindOnce(&EjectPathOnBlockingTaskRunner, path, device),
332       std::move(callback));
333 }
334
335 void StorageMonitorLinux::OnMtabWatcherCreated(
336     std::unique_ptr<MtabWatcherLinux> watcher) {
337   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
338   mtab_watcher_ = std::move(watcher);
339 }
340
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
353     // |mount_point|.
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());
364         }
365         if (priority->second.size() > 1)
366           multiple_mounted_devices_needing_reattachment.push_back(mount_device);
367       }
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);
372     }
373   }
374
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();
381        ++it) {
382     mount_info_map_.erase(*it);
383   }
384
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
387   // mount points.
388   for (std::list<base::FilePath>::const_iterator it =
389            multiple_mounted_devices_needing_reattachment.begin();
390        it != multiple_mounted_devices_needing_reattachment.end();
391        ++it) {
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;
395
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);
400   }
401
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();
407        ++new_iter) {
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
414       // device.
415       if (IsDeviceAlreadyMounted(mount_device)) {
416         HandleDeviceMountedMultipleTimes(mount_device, mount_point);
417       } else {
418         mounting_task_runner->PostTaskAndReplyWithResult(
419             FROM_HERE,
420             base::BindOnce(get_device_info_callback_, mount_device,
421                            mount_point),
422             base::BindOnce(&StorageMonitorLinux::AddNewMount,
423                            weak_ptr_factory_.GetWeakPtr(), mount_device));
424       }
425     }
426   }
427
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()));
438   }
439 }
440
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);
445 }
446
447 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
448     const base::FilePath& mount_device,
449     const base::FilePath& mount_point) {
450   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
451
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;
458 }
459
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_);
464
465   if (!storage_info)
466     return;
467
468   DCHECK(!storage_info->device_id().empty());
469
470   bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
471   const base::FilePath mount_point(storage_info->location());
472
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);
479 }
480
481 StorageMonitor* StorageMonitor::CreateInternal() {
482   const base::FilePath kDefaultMtabPath("/etc/mtab");
483   return new StorageMonitorLinux(kDefaultMtabPath);
484 }
485
486 }  // namespace storage_monitor