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