d2bcea1cb5a85d9d371b30f5f98f5a8cb2848392
[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/apple/bridging.h"
12 #include "base/apple/foundation_util.h"
13 #include "base/functional/bind.h"
14 #include "base/mac/mac_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/task/task_traits.h"
18 #include "base/task/thread_pool.h"
19 #include "base/threading/scoped_blocking_call.h"
20 #include "components/storage_monitor/image_capture_device_manager.h"
21 #include "components/storage_monitor/media_storage_util.h"
22 #include "components/storage_monitor/storage_info.h"
23 #include "content/public/browser/browser_task_traits.h"
24 #include "content/public/browser/browser_thread.h"
25
26 namespace storage_monitor {
27
28 namespace {
29
30 const char16_t kDiskImageModelName[] = u"Disk Image";
31
32 std::u16string GetUTF16FromDictionary(CFDictionaryRef dictionary,
33                                       CFStringRef key) {
34   CFStringRef value =
35       base::apple::GetValueFromDictionary<CFStringRef>(dictionary, key);
36   if (!value)
37     return std::u16string();
38   return base::SysCFStringRefToUTF16(value);
39 }
40
41 std::u16string JoinName(const std::u16string& name,
42                         const std::u16string& addition) {
43   if (addition.empty())
44     return name;
45   if (name.empty())
46     return addition;
47   return name + u' ' + addition;
48 }
49
50 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
51   if (!is_removable)
52     return StorageInfo::FIXED_MASS_STORAGE;
53   if (has_dcim)
54     return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
55   return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
56 }
57
58 StorageInfo BuildStorageInfo(
59     CFDictionaryRef dict, std::string* bsd_name) {
60   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
61                                                 base::BlockingType::MAY_BLOCK);
62
63   CFStringRef device_bsd_name =
64       base::apple::GetValueFromDictionary<CFStringRef>(
65           dict, kDADiskDescriptionMediaBSDNameKey);
66   if (device_bsd_name && bsd_name)
67     *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
68
69   CFURLRef url = base::apple::GetValueFromDictionary<CFURLRef>(
70       dict, kDADiskDescriptionVolumePathKey);
71   base::FilePath location =
72       base::apple::NSURLToFilePath(base::apple::CFToNSPtrCast(url));
73   CFNumberRef size_number = base::apple::GetValueFromDictionary<CFNumberRef>(
74       dict, kDADiskDescriptionMediaSizeKey);
75   uint64_t size_in_bytes = 0;
76   if (size_number)
77     CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
78
79   std::u16string vendor =
80       GetUTF16FromDictionary(dict, kDADiskDescriptionDeviceVendorKey);
81   std::u16string model =
82       GetUTF16FromDictionary(dict, kDADiskDescriptionDeviceModelKey);
83   std::u16string label =
84       GetUTF16FromDictionary(dict, kDADiskDescriptionVolumeNameKey);
85
86   CFUUIDRef uuid = base::apple::GetValueFromDictionary<CFUUIDRef>(
87       dict, kDADiskDescriptionVolumeUUIDKey);
88   std::string unique_id;
89   if (uuid) {
90     base::apple::ScopedCFTypeRef<CFStringRef> uuid_string(
91         CFUUIDCreateString(nullptr, uuid));
92     if (uuid_string.get())
93       unique_id = base::SysCFStringRefToUTF8(uuid_string);
94   }
95   if (unique_id.empty()) {
96     std::u16string revision =
97         GetUTF16FromDictionary(dict, kDADiskDescriptionDeviceRevisionKey);
98     std::u16string unique_id2 = vendor;
99     unique_id2 = JoinName(unique_id2, model);
100     unique_id2 = JoinName(unique_id2, revision);
101     unique_id = base::UTF16ToUTF8(unique_id2);
102   }
103
104   CFBooleanRef is_removable_ref =
105       base::apple::GetValueFromDictionary<CFBooleanRef>(
106           dict, kDADiskDescriptionMediaRemovableKey);
107   bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
108   // Checking for DCIM only matters on removable devices.
109   bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
110   StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
111   std::string device_id;
112   if (!unique_id.empty())
113     device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
114
115   return StorageInfo(device_id, location.value(), label, vendor, model,
116                      size_in_bytes);
117 }
118
119 struct EjectDiskOptions {
120   std::string bsd_name;
121   base::OnceCallback<void(StorageMonitor::EjectStatus)> callback;
122   base::apple::ScopedCFTypeRef<DADiskRef> disk;
123 };
124
125 void PostEjectCallback(DADiskRef disk,
126                        DADissenterRef dissenter,
127                        void* context) {
128   std::unique_ptr<EjectDiskOptions> options_deleter(
129       static_cast<EjectDiskOptions*>(context));
130   if (dissenter) {
131     std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
132     return;
133   }
134
135   std::move(options_deleter->callback).Run(StorageMonitor::EJECT_OK);
136 }
137
138 void PostUnmountCallback(DADiskRef disk,
139                          DADissenterRef dissenter,
140                          void* context) {
141   std::unique_ptr<EjectDiskOptions> options_deleter(
142       static_cast<EjectDiskOptions*>(context));
143   if (dissenter) {
144     std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
145     return;
146   }
147
148   DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
149               PostEjectCallback, options_deleter.release());
150 }
151
152 void EjectDisk(EjectDiskOptions* options) {
153   DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
154                 PostUnmountCallback, options);
155 }
156
157 }  // namespace
158
159 StorageMonitorMac::StorageMonitorMac() = default;
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(nullptr));
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::apple::ScopedCFTypeRef<DADiskRef> disk(
295       DADiskCreateFromBSDName(nullptr, 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::apple::ScopedCFTypeRef<CFDictionaryRef> dict(
343       DADiskCopyDescription(disk));
344   std::string* bsd_name = new std::string;
345   base::ThreadPool::PostTaskAndReplyWithResult(
346       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
347       base::BindOnce(&BuildStorageInfo, dict, bsd_name),
348       base::BindOnce(&StorageMonitorMac::UpdateDisk, AsWeakPtr(), update_type,
349                      base::Owned(bsd_name)));
350 }
351
352
353 bool StorageMonitorMac::ShouldPostNotificationForDisk(
354     const StorageInfo& info) const {
355   // Only post notifications about disks that have no empty fields and
356   // are removable. Also exclude disk images (DMGs).
357   return !info.device_id().empty() && !info.location().empty() &&
358          info.model_name() != kDiskImageModelName &&
359          StorageInfo::IsMassStorageDevice(info.device_id());
360 }
361
362 bool StorageMonitorMac::FindDiskWithMountPoint(
363     const base::FilePath& mount_point,
364     StorageInfo* info) const {
365   for (const auto& disk_info : disk_info_map_) {
366     if (disk_info.second.location() == mount_point.value()) {
367       *info = disk_info.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