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.
5 #include "components/storage_monitor/storage_monitor_mac.h"
11 #include "base/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"
25 namespace storage_monitor {
29 const char16_t kDiskImageModelName[] = u"Disk Image";
31 std::u16string GetUTF16FromDictionary(CFDictionaryRef dictionary,
34 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
36 return std::u16string();
37 return base::SysCFStringRefToUTF16(value);
40 std::u16string JoinName(const std::u16string& name,
41 const std::u16string& addition) {
46 return name + u' ' + addition;
49 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
51 return StorageInfo::FIXED_MASS_STORAGE;
53 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
54 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
57 StorageInfo BuildStorageInfo(
58 CFDictionaryRef dict, std::string* bsd_name) {
59 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
60 base::BlockingType::MAY_BLOCK);
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);
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;
76 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
78 std::u16string vendor =
79 GetUTF16FromDictionary(dict, kDADiskDescriptionDeviceVendorKey);
80 std::u16string model =
81 GetUTF16FromDictionary(dict, kDADiskDescriptionDeviceModelKey);
82 std::u16string label =
83 GetUTF16FromDictionary(dict, kDADiskDescriptionVolumeNameKey);
85 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
86 dict, kDADiskDescriptionVolumeUUIDKey);
87 std::string unique_id;
89 base::ScopedCFTypeRef<CFStringRef> uuid_string(
90 CFUUIDCreateString(NULL, uuid));
91 if (uuid_string.get())
92 unique_id = base::SysCFStringRefToUTF8(uuid_string);
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);
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);
114 return StorageInfo(device_id, location.value(), label, vendor, model,
118 struct EjectDiskOptions {
119 std::string bsd_name;
120 base::OnceCallback<void(StorageMonitor::EjectStatus)> callback;
121 base::ScopedCFTypeRef<DADiskRef> disk;
124 void PostEjectCallback(DADiskRef disk,
125 DADissenterRef dissenter,
127 std::unique_ptr<EjectDiskOptions> options_deleter(
128 static_cast<EjectDiskOptions*>(context));
130 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
134 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_OK);
137 void PostUnmountCallback(DADiskRef disk,
138 DADissenterRef dissenter,
140 std::unique_ptr<EjectDiskOptions> options_deleter(
141 static_cast<EjectDiskOptions*>(context));
143 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
147 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
148 PostEjectCallback, options_deleter.release());
151 void EjectDisk(EjectDiskOptions* options) {
152 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
153 PostUnmountCallback, options);
158 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
161 StorageMonitorMac::~StorageMonitorMac() {
162 if (session_.get()) {
163 DASessionUnscheduleFromRunLoop(
164 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
168 void StorageMonitorMac::Init() {
169 session_.reset(DASessionCreate(NULL));
171 // Register for callbacks for attached, changed, and removed devices.
172 // This will send notifications for existing devices too.
173 DARegisterDiskAppearedCallback(
175 kDADiskDescriptionMatchVolumeMountable,
176 DiskAppearedCallback,
178 DARegisterDiskDisappearedCallback(
180 kDADiskDescriptionMatchVolumeMountable,
181 DiskDisappearedCallback,
183 DARegisterDiskDescriptionChangedCallback(
185 kDADiskDescriptionMatchVolumeMountable,
186 kDADiskDescriptionWatchVolumePath,
187 DiskDescriptionChangedCallback,
190 DASessionScheduleWithRunLoop(
191 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
193 image_capture_device_manager_ = std::make_unique<ImageCaptureDeviceManager>();
194 image_capture_device_manager_->SetNotifications(receiver());
197 void StorageMonitorMac::UpdateDisk(UpdateType update_type,
198 std::string* bsd_name,
199 const StorageInfo& info) {
200 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
203 pending_disk_updates_--;
204 bool initialization_complete = false;
205 if (!IsInitialized() && pending_disk_updates_ == 0)
206 initialization_complete = true;
208 if (info.device_id().empty() || bsd_name->empty()) {
209 if (initialization_complete)
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());
225 if (update_type == UPDATE_DEVICE_REMOVED) {
226 if (it != disk_info_map_.end())
227 disk_info_map_.erase(it);
229 disk_info_map_[*bsd_name] = info;
230 if (ShouldPostNotificationForDisk(info))
231 receiver()->ProcessAttach(info);
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)
240 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
241 StorageInfo* device_info) const {
244 if (!path.IsAbsolute())
247 base::FilePath current = path;
248 const base::FilePath root(base::FilePath::kSeparators);
249 while (current != root) {
251 if (FindDiskWithMountPoint(current, &info)) {
255 current = current.DirName();
261 void StorageMonitorMac::EjectDevice(
262 const std::string& device_id,
263 base::OnceCallback<void(EjectStatus)> callback) {
264 StorageInfo::Type type;
266 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
267 std::move(callback).Run(EJECT_FAILURE);
271 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
272 image_capture_device_manager_.get()) {
273 image_capture_device_manager_->EjectDevice(uuid, std::move(callback));
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);
287 if (bsd_name.empty()) {
288 std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
292 receiver()->ProcessDetach(device_id);
294 base::ScopedCFTypeRef<DADiskRef> disk(
295 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
297 std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
300 // Get the reference to the full disk for ejecting.
301 disk.reset(DADiskCopyWholeDisk(disk));
303 std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
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));
316 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
317 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
318 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
322 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
323 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
324 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
328 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
331 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
332 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
335 void StorageMonitorMac::GetDiskInfoAndUpdate(
337 StorageMonitorMac::UpdateType update_type) {
338 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
340 pending_disk_updates_++;
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)));
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());
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()) {
374 StorageMonitor* StorageMonitor::CreateInternal() {
375 return new StorageMonitorMac();
378 } // namespace storage_monitor