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