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 #include "components/storage_monitor/storage_monitor_chromeos.h"
10 #include "base/containers/contains.h"
11 #include "base/files/file_path.h"
12 #include "base/functional/bind.h"
13 #include "base/functional/callback_helpers.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/task/sequenced_task_runner.h"
16 #include "base/task/task_traits.h"
17 #include "base/task/thread_pool.h"
18 #include "chromeos/ash/components/disks/disk.h"
19 #include "components/storage_monitor/media_storage_util.h"
20 #include "components/storage_monitor/mtp_manager_client_chromeos.h"
21 #include "components/storage_monitor/removable_device_constants.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/device_service.h"
25 namespace storage_monitor {
29 using ::ash::disks::Disk;
30 using ::ash::disks::DiskMountManager;
32 // Constructs a device id using uuid or manufacturer (vendor and product) id
34 std::string MakeDeviceUniqueId(const Disk& disk) {
35 std::string uuid = disk.fs_uuid();
37 return kFSUniqueIdPrefix + uuid;
39 // If one of the vendor or product information is missing, its value in the
41 // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialInfo
42 // TODO(kmadhusu) Extract serial information for the disks and append it to
43 // the device unique id.
44 const std::string& vendor = disk.vendor_id();
45 const std::string& product = disk.product_id();
46 if (vendor.empty() && product.empty())
48 return kVendorModelSerialPrefix + vendor + ":" + product + ":";
51 // Returns whether the requested device is valid. On success |info| will contain
52 // device information.
53 bool GetDeviceInfo(const DiskMountManager::MountPoint& mount_info,
57 std::string source_path = mount_info.source_path;
60 DiskMountManager::GetInstance()->FindDiskBySourcePath(source_path);
61 if (!disk || disk->device_type() == ash::DeviceType::kUnknown)
64 std::string unique_id = MakeDeviceUniqueId(*disk);
65 if (unique_id.empty())
68 StorageInfo::Type type = has_dcim ?
69 StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM :
70 StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
73 StorageInfo::MakeDeviceId(type, unique_id), mount_info.mount_path,
74 base::UTF8ToUTF16(disk->device_label()),
75 base::UTF8ToUTF16(disk->vendor_name()),
76 base::UTF8ToUTF16(disk->product_name()), disk->total_size_in_bytes());
80 // Returns whether the requested device is valid. On success |info| will contain
81 // fixed storage device information.
82 bool GetFixedStorageInfo(const Disk& disk, StorageInfo* info) {
85 std::string unique_id = MakeDeviceUniqueId(disk);
86 if (unique_id.empty())
90 StorageInfo::MakeDeviceId(StorageInfo::FIXED_MASS_STORAGE, unique_id),
91 disk.mount_path(), base::UTF8ToUTF16(disk.device_label()),
92 base::UTF8ToUTF16(disk.vendor_name()),
93 base::UTF8ToUTF16(disk.product_name()), disk.total_size_in_bytes());
99 StorageMonitorCros::StorageMonitorCros() = default;
101 StorageMonitorCros::~StorageMonitorCros() {
102 DiskMountManager* manager = DiskMountManager::GetInstance();
104 manager->RemoveObserver(this);
108 void StorageMonitorCros::Init() {
109 DCHECK(DiskMountManager::GetInstance());
110 DiskMountManager::GetInstance()->AddObserver(this);
111 CheckExistingMountPoints();
113 // Tests may have already set a MTP manager.
114 if (!mtp_device_manager_) {
115 // Set up the connection with mojofied MtpManager.
116 content::GetDeviceService().BindMtpManager(
117 mtp_device_manager_.BindNewPipeAndPassReceiver());
119 // |mtp_manager_client_| needs to be initialized for both tests and
120 // production code, so keep it out of the if condition.
121 mtp_manager_client_ = std::make_unique<MtpManagerClientChromeOS>(
122 receiver(), mtp_device_manager_.get());
125 void StorageMonitorCros::CheckExistingMountPoints() {
126 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
128 for (const auto& disk : DiskMountManager::GetInstance()->disks()) {
129 if (disk->IsStatefulPartition()) {
130 AddFixedStorageDisk(*disk);
135 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner =
136 base::ThreadPool::CreateSequencedTaskRunner(
137 {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
139 for (const auto& mount_point :
140 DiskMountManager::GetInstance()->mount_points()) {
141 blocking_task_runner->PostTaskAndReplyWithResult(
143 base::BindOnce(&MediaStorageUtil::HasDcim,
144 base::FilePath(mount_point.mount_path)),
145 base::BindOnce(&StorageMonitorCros::AddMountedPath,
146 weak_ptr_factory_.GetWeakPtr(), mount_point));
149 // Note: Relies on scheduled tasks on the |blocking_task_runner| being
150 // sequential. This block needs to follow the for loop, so that the DoNothing
151 // call on the |blocking_task_runner| happens after the scheduled metadata
152 // retrievals, meaning that the reply callback will then happen after all the
153 // AddMountedPath calls.
155 blocking_task_runner->PostTaskAndReply(
156 FROM_HERE, base::DoNothing(),
157 base::BindOnce(&StorageMonitorCros::MarkInitialized,
158 weak_ptr_factory_.GetWeakPtr()));
161 void StorageMonitorCros::OnBootDeviceDiskEvent(
162 DiskMountManager::DiskEvent event,
164 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
166 if (!disk.IsStatefulPartition())
170 case DiskMountManager::DiskEvent::DISK_ADDED: {
171 AddFixedStorageDisk(disk);
174 case DiskMountManager::DiskEvent::DISK_REMOVED: {
175 RemoveFixedStorageDisk(disk);
178 case DiskMountManager::DiskEvent::DISK_CHANGED: {
179 // Although boot disks never change, this event is fired when the device
180 // is woken from suspend and re-enumerates the set of disks. The event
181 // could be changed to only fire when an actual change occurs, but that's
182 // not currently possible because the "re-enumerate on wake" behaviour is
183 // relied on to re-mount external media that was unmounted when the system
190 void StorageMonitorCros::OnMountEvent(
191 DiskMountManager::MountEvent event,
192 ash::MountError error_code,
193 const DiskMountManager::MountPoint& mount_info) {
194 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
196 // Ignore mount points that are not devices.
197 if (mount_info.mount_type != ash::MountType::kDevice)
200 if (error_code != ash::MountError::kSuccess)
202 if (mount_info.mount_error != ash::MountError::kSuccess)
206 case DiskMountManager::MOUNTING: {
207 if (base::Contains(mount_map_, mount_info.mount_path)) {
211 base::ThreadPool::PostTaskAndReplyWithResult(
212 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
213 base::BindOnce(&MediaStorageUtil::HasDcim,
214 base::FilePath(mount_info.mount_path)),
215 base::BindOnce(&StorageMonitorCros::AddMountedPath,
216 weak_ptr_factory_.GetWeakPtr(), mount_info));
219 case DiskMountManager::UNMOUNTING: {
220 MountMap::iterator it = mount_map_.find(mount_info.mount_path);
221 if (it == mount_map_.end())
223 receiver()->ProcessDetach(it->second.device_id());
224 mount_map_.erase(it);
230 void StorageMonitorCros::SetMediaTransferProtocolManagerForTest(
231 mojo::PendingRemote<device::mojom::MtpManager> test_manager) {
232 DCHECK(!mtp_device_manager_);
233 mtp_device_manager_.Bind(std::move(test_manager));
236 bool StorageMonitorCros::GetStorageInfoForPath(
237 const base::FilePath& path,
238 StorageInfo* device_info) const {
241 if (mtp_manager_client_->GetStorageInfoForPath(path, device_info)) {
245 if (!path.IsAbsolute())
248 base::FilePath current = path;
249 while (!base::Contains(mount_map_, current.value()) &&
250 current != current.DirName()) {
251 current = current.DirName();
254 MountMap::const_iterator info_it = mount_map_.find(current.value());
255 if (info_it == mount_map_.end())
258 *device_info = info_it->second;
262 // Callback executed when the unmount call is run by DiskMountManager.
263 // Forwards result to |EjectDevice| caller.
264 void NotifyUnmountResult(
265 base::OnceCallback<void(StorageMonitor::EjectStatus)> callback,
266 ash::MountError error_code) {
267 if (error_code == ash::MountError::kSuccess)
268 std::move(callback).Run(StorageMonitor::EJECT_OK);
270 std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
273 void StorageMonitorCros::EjectDevice(
274 const std::string& device_id,
275 base::OnceCallback<void(EjectStatus)> callback) {
276 StorageInfo::Type type;
277 if (!StorageInfo::CrackDeviceId(device_id, &type, nullptr)) {
278 std::move(callback).Run(EJECT_FAILURE);
282 if (type == StorageInfo::MTP_OR_PTP) {
283 mtp_manager_client_->EjectDevice(device_id, std::move(callback));
287 std::string mount_path;
288 for (MountMap::const_iterator info_it = mount_map_.begin();
289 info_it != mount_map_.end(); ++info_it) {
290 if (info_it->second.device_id() == device_id)
291 mount_path = info_it->first;
294 if (mount_path.empty()) {
295 std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
299 DiskMountManager* manager = DiskMountManager::GetInstance();
301 std::move(callback).Run(EJECT_FAILURE);
305 manager->UnmountPath(
306 mount_path, base::BindOnce(NotifyUnmountResult, std::move(callback)));
309 device::mojom::MtpManager*
310 StorageMonitorCros::media_transfer_protocol_manager() {
311 return mtp_device_manager_.get();
314 void StorageMonitorCros::AddMountedPath(
315 const DiskMountManager::MountPoint& mount_info,
317 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
319 if (base::Contains(mount_map_, mount_info.mount_path)) {
320 // CheckExistingMountPoints() added the mount point information in the map
321 // before the device attached handler is called. Therefore, an entry for
322 // the device already exists in the map.
326 // Get the media device uuid and label if exists.
328 if (!GetDeviceInfo(mount_info, has_dcim, &info))
331 if (info.device_id().empty())
334 mount_map_.insert(std::make_pair(mount_info.mount_path, info));
336 receiver()->ProcessAttach(info);
339 void StorageMonitorCros::AddFixedStorageDisk(const Disk& disk) {
340 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
341 DCHECK(disk.IsStatefulPartition());
344 if (!GetFixedStorageInfo(disk, &info))
347 if (base::Contains(mount_map_, disk.mount_path()))
350 mount_map_.insert(std::make_pair(disk.mount_path(), info));
351 receiver()->ProcessAttach(info);
354 void StorageMonitorCros::RemoveFixedStorageDisk(const Disk& disk) {
355 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
356 DCHECK(disk.IsStatefulPartition());
359 if (!GetFixedStorageInfo(disk, &info))
362 size_t erased_count = mount_map_.erase(disk.mount_path());
366 receiver()->ProcessDetach((info.device_id()));
369 StorageMonitor* StorageMonitor::CreateInternal() {
370 return new StorageMonitorCros();
373 } // namespace storage_monitor