Upload upstream chromium 85.0.4183.84
[platform/framework/web/chromium-efl.git] / components / storage_monitor / storage_monitor_mac.mm
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.
4
5 #include "components/storage_monitor/storage_monitor_mac.h"
6
7 #include <stdint.h>
8
9 #include "base/bind.h"
10 #include "base/mac/foundation_util.h"
11 #include "base/mac/mac_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/task/task_traits.h"
15 #include "base/task/thread_pool.h"
16 #include "base/threading/scoped_blocking_call.h"
17 #include "components/storage_monitor/image_capture_device_manager.h"
18 #include "components/storage_monitor/media_storage_util.h"
19 #include "components/storage_monitor/storage_info.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22
23 namespace storage_monitor {
24
25 namespace {
26
27 const char kDiskImageModelName[] = "Disk Image";
28
29 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
30                                       CFStringRef key) {
31   CFStringRef value =
32       base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
33   if (!value)
34     return base::string16();
35   return base::SysCFStringRefToUTF16(value);
36 }
37
38 base::string16 JoinName(const base::string16& name,
39                         const base::string16& addition) {
40   if (addition.empty())
41     return name;
42   if (name.empty())
43     return addition;
44   return name + static_cast<base::char16>(' ') + addition;
45 }
46
47 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
48   if (!is_removable)
49     return StorageInfo::FIXED_MASS_STORAGE;
50   if (has_dcim)
51     return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
52   return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
53 }
54
55 StorageInfo BuildStorageInfo(
56     CFDictionaryRef dict, std::string* bsd_name) {
57   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
58                                                 base::BlockingType::MAY_BLOCK);
59
60   CFStringRef device_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
61       dict, kDADiskDescriptionMediaBSDNameKey);
62   if (device_bsd_name && bsd_name)
63     *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
64
65   CFURLRef url = base::mac::GetValueFromDictionary<CFURLRef>(
66       dict, kDADiskDescriptionVolumePathKey);
67   NSURL* nsurl = base::mac::CFToNSCast(url);
68   base::FilePath location = base::mac::NSStringToFilePath([nsurl path]);
69   CFNumberRef size_number =
70       base::mac::GetValueFromDictionary<CFNumberRef>(
71           dict, kDADiskDescriptionMediaSizeKey);
72   uint64_t size_in_bytes = 0;
73   if (size_number)
74     CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
75
76   base::string16 vendor = GetUTF16FromDictionary(
77       dict, kDADiskDescriptionDeviceVendorKey);
78   base::string16 model = GetUTF16FromDictionary(
79       dict, kDADiskDescriptionDeviceModelKey);
80   base::string16 label = GetUTF16FromDictionary(
81       dict, kDADiskDescriptionVolumeNameKey);
82
83   CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
84       dict, kDADiskDescriptionVolumeUUIDKey);
85   std::string unique_id;
86   if (uuid) {
87     base::ScopedCFTypeRef<CFStringRef> uuid_string(
88         CFUUIDCreateString(NULL, uuid));
89     if (uuid_string.get())
90       unique_id = base::SysCFStringRefToUTF8(uuid_string);
91   }
92   if (unique_id.empty()) {
93     base::string16 revision = GetUTF16FromDictionary(
94         dict, kDADiskDescriptionDeviceRevisionKey);
95     base::string16 unique_id2 = vendor;
96     unique_id2 = JoinName(unique_id2, model);
97     unique_id2 = JoinName(unique_id2, revision);
98     unique_id = base::UTF16ToUTF8(unique_id2);
99   }
100
101   CFBooleanRef is_removable_ref =
102       base::mac::GetValueFromDictionary<CFBooleanRef>(
103           dict, kDADiskDescriptionMediaRemovableKey);
104   bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
105   // Checking for DCIM only matters on removable devices.
106   bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
107   StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
108   std::string device_id;
109   if (!unique_id.empty())
110     device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
111
112   return StorageInfo(device_id, location.value(), label, vendor, model,
113                      size_in_bytes);
114 }
115
116 struct EjectDiskOptions {
117   std::string bsd_name;
118   base::OnceCallback<void(StorageMonitor::EjectStatus)> callback;
119   base::ScopedCFTypeRef<DADiskRef> disk;
120 };
121
122 void PostEjectCallback(DADiskRef disk,
123                        DADissenterRef dissenter,
124                        void* context) {
125   std::unique_ptr<EjectDiskOptions> options_deleter(
126       static_cast<EjectDiskOptions*>(context));
127   if (dissenter) {
128     std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
129     return;
130   }
131
132   std::move(options_deleter->callback).Run(StorageMonitor::EJECT_OK);
133 }
134
135 void PostUnmountCallback(DADiskRef disk,
136                          DADissenterRef dissenter,
137                          void* context) {
138   std::unique_ptr<EjectDiskOptions> options_deleter(
139       static_cast<EjectDiskOptions*>(context));
140   if (dissenter) {
141     std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
142     return;
143   }
144
145   DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
146               PostEjectCallback, options_deleter.release());
147 }
148
149 void EjectDisk(EjectDiskOptions* options) {
150   DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
151                 PostUnmountCallback, options);
152 }
153
154 }  // namespace
155
156 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
157 }
158
159 StorageMonitorMac::~StorageMonitorMac() {
160   if (session_.get()) {
161     DASessionUnscheduleFromRunLoop(
162         session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
163   }
164 }
165
166 void StorageMonitorMac::Init() {
167   session_.reset(DASessionCreate(NULL));
168
169   // Register for callbacks for attached, changed, and removed devices.
170   // This will send notifications for existing devices too.
171   DARegisterDiskAppearedCallback(
172       session_,
173       kDADiskDescriptionMatchVolumeMountable,
174       DiskAppearedCallback,
175       this);
176   DARegisterDiskDisappearedCallback(
177       session_,
178       kDADiskDescriptionMatchVolumeMountable,
179       DiskDisappearedCallback,
180       this);
181   DARegisterDiskDescriptionChangedCallback(
182       session_,
183       kDADiskDescriptionMatchVolumeMountable,
184       kDADiskDescriptionWatchVolumePath,
185       DiskDescriptionChangedCallback,
186       this);
187
188   DASessionScheduleWithRunLoop(
189       session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
190
191   image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
192   image_capture_device_manager_->SetNotifications(receiver());
193 }
194
195 void StorageMonitorMac::UpdateDisk(UpdateType update_type,
196                                    std::string* bsd_name,
197                                    const StorageInfo& info) {
198   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
199   DCHECK(bsd_name);
200
201   pending_disk_updates_--;
202   bool initialization_complete = false;
203   if (!IsInitialized() && pending_disk_updates_ == 0)
204     initialization_complete = true;
205
206   if (info.device_id().empty() || bsd_name->empty()) {
207     if (initialization_complete)
208       MarkInitialized();
209     return;
210   }
211
212   std::map<std::string, StorageInfo>::iterator it =
213       disk_info_map_.find(*bsd_name);
214   if (it != disk_info_map_.end()) {
215     // If an attached notification was previously posted then post a detached
216     // notification now. This is used for devices that are being removed or
217     // devices that have changed.
218     if (ShouldPostNotificationForDisk(it->second)) {
219       receiver()->ProcessDetach(it->second.device_id());
220     }
221   }
222
223   if (update_type == UPDATE_DEVICE_REMOVED) {
224     if (it != disk_info_map_.end())
225       disk_info_map_.erase(it);
226   } else {
227     disk_info_map_[*bsd_name] = info;
228     if (ShouldPostNotificationForDisk(info))
229       receiver()->ProcessAttach(info);
230   }
231
232   // We're not really honestly sure we're done, but this looks the best we
233   // can do. Any misses should go out through notifications.
234   if (initialization_complete)
235     MarkInitialized();
236 }
237
238 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
239                                               StorageInfo* device_info) const {
240   DCHECK(device_info);
241
242   if (!path.IsAbsolute())
243     return false;
244
245   base::FilePath current = path;
246   const base::FilePath root(base::FilePath::kSeparators);
247   while (current != root) {
248     StorageInfo info;
249     if (FindDiskWithMountPoint(current, &info)) {
250       *device_info = info;
251       return true;
252     }
253     current = current.DirName();
254   }
255
256   return false;
257 }
258
259 void StorageMonitorMac::EjectDevice(
260     const std::string& device_id,
261     base::OnceCallback<void(EjectStatus)> callback) {
262   StorageInfo::Type type;
263   std::string uuid;
264   if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
265     std::move(callback).Run(EJECT_FAILURE);
266     return;
267   }
268
269   if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
270       image_capture_device_manager_.get()) {
271     image_capture_device_manager_->EjectDevice(uuid, std::move(callback));
272     return;
273   }
274
275   std::string bsd_name;
276   for (std::map<std::string, StorageInfo>::iterator
277       it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
278     if (it->second.device_id() == device_id) {
279       bsd_name = it->first;
280       disk_info_map_.erase(it);
281       break;
282     }
283   }
284
285   if (bsd_name.empty()) {
286     std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
287     return;
288   }
289
290   receiver()->ProcessDetach(device_id);
291
292   base::ScopedCFTypeRef<DADiskRef> disk(
293       DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
294   if (!disk.get()) {
295     std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
296     return;
297   }
298   // Get the reference to the full disk for ejecting.
299   disk.reset(DADiskCopyWholeDisk(disk));
300   if (!disk.get()) {
301     std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
302     return;
303   }
304
305   EjectDiskOptions* options = new EjectDiskOptions;
306   options->bsd_name = bsd_name;
307   options->callback = std::move(callback);
308   options->disk = std::move(disk);
309   content::GetUIThreadTaskRunner({})->PostTask(
310       FROM_HERE, base::BindOnce(EjectDisk, options));
311 }
312
313 // static
314 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
315   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
316   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
317 }
318
319 // static
320 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
321   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
322   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
323 }
324
325 // static
326 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
327                                                        CFArrayRef keys,
328                                                        void *context) {
329   StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
330   monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
331 }
332
333 void StorageMonitorMac::GetDiskInfoAndUpdate(
334     DADiskRef disk,
335     StorageMonitorMac::UpdateType update_type) {
336   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
337
338   pending_disk_updates_++;
339
340   base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
341   std::string* bsd_name = new std::string;
342   base::ThreadPool::PostTaskAndReplyWithResult(
343       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
344       base::BindOnce(&BuildStorageInfo, dict, bsd_name),
345       base::BindOnce(&StorageMonitorMac::UpdateDisk, AsWeakPtr(), update_type,
346                      base::Owned(bsd_name)));
347 }
348
349
350 bool StorageMonitorMac::ShouldPostNotificationForDisk(
351     const StorageInfo& info) const {
352   // Only post notifications about disks that have no empty fields and
353   // are removable. Also exclude disk images (DMGs).
354   return !info.device_id().empty() &&
355          !info.location().empty() &&
356          info.model_name() != base::ASCIIToUTF16(kDiskImageModelName) &&
357          StorageInfo::IsMassStorageDevice(info.device_id());
358 }
359
360 bool StorageMonitorMac::FindDiskWithMountPoint(
361     const base::FilePath& mount_point,
362     StorageInfo* info) const {
363   for (std::map<std::string, StorageInfo>::const_iterator
364       it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
365     if (it->second.location() == mount_point.value()) {
366       *info = it->second;
367       return true;
368     }
369   }
370   return false;
371 }
372
373 StorageMonitor* StorageMonitor::CreateInternal() {
374   return new StorageMonitorMac();
375 }
376
377 }  // namespace storage_monitor