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"
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"
21 namespace storage_monitor {
25 const char kDiskImageModelName[] = "Disk Image";
27 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
30 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
32 return base::string16();
33 return base::SysCFStringRefToUTF16(value);
36 base::string16 JoinName(const base::string16& name,
37 const base::string16& addition) {
42 return name + static_cast<base::char16>(' ') + addition;
45 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
47 return StorageInfo::FIXED_MASS_STORAGE;
49 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
50 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
53 StorageInfo BuildStorageInfo(
54 CFDictionaryRef dict, std::string* bsd_name) {
55 base::AssertBlockingAllowed();
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);
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;
71 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
73 base::string16 vendor = GetUTF16FromDictionary(
74 dict, kDADiskDescriptionDeviceVendorKey);
75 base::string16 model = GetUTF16FromDictionary(
76 dict, kDADiskDescriptionDeviceModelKey);
77 base::string16 label = GetUTF16FromDictionary(
78 dict, kDADiskDescriptionVolumeNameKey);
80 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
81 dict, kDADiskDescriptionVolumeUUIDKey);
82 std::string unique_id;
84 base::ScopedCFTypeRef<CFStringRef> uuid_string(
85 CFUUIDCreateString(NULL, uuid));
86 if (uuid_string.get())
87 unique_id = base::SysCFStringRefToUTF8(uuid_string);
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);
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);
109 return StorageInfo(device_id, location.value(), label, vendor, model,
113 struct EjectDiskOptions {
114 std::string bsd_name;
115 base::Callback<void(StorageMonitor::EjectStatus)> callback;
116 base::ScopedCFTypeRef<DADiskRef> disk;
119 void PostEjectCallback(DADiskRef disk,
120 DADissenterRef dissenter,
122 std::unique_ptr<EjectDiskOptions> options_deleter(
123 static_cast<EjectDiskOptions*>(context));
125 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
129 options_deleter->callback.Run(StorageMonitor::EJECT_OK);
132 void PostUnmountCallback(DADiskRef disk,
133 DADissenterRef dissenter,
135 std::unique_ptr<EjectDiskOptions> options_deleter(
136 static_cast<EjectDiskOptions*>(context));
138 options_deleter->callback.Run(StorageMonitor::EJECT_IN_USE);
142 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
143 PostEjectCallback, options_deleter.release());
146 void EjectDisk(EjectDiskOptions* options) {
147 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
148 PostUnmountCallback, options);
153 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
156 StorageMonitorMac::~StorageMonitorMac() {
157 if (session_.get()) {
158 DASessionUnscheduleFromRunLoop(
159 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
163 void StorageMonitorMac::Init() {
164 session_.reset(DASessionCreate(NULL));
166 // Register for callbacks for attached, changed, and removed devices.
167 // This will send notifications for existing devices too.
168 DARegisterDiskAppearedCallback(
170 kDADiskDescriptionMatchVolumeMountable,
171 DiskAppearedCallback,
173 DARegisterDiskDisappearedCallback(
175 kDADiskDescriptionMatchVolumeMountable,
176 DiskDisappearedCallback,
178 DARegisterDiskDescriptionChangedCallback(
180 kDADiskDescriptionMatchVolumeMountable,
181 kDADiskDescriptionWatchVolumePath,
182 DiskDescriptionChangedCallback,
185 DASessionScheduleWithRunLoop(
186 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
188 image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
189 image_capture_device_manager_->SetNotifications(receiver());
192 void StorageMonitorMac::UpdateDisk(UpdateType update_type,
193 std::string* bsd_name,
194 const StorageInfo& info) {
195 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
198 pending_disk_updates_--;
199 bool initialization_complete = false;
200 if (!IsInitialized() && pending_disk_updates_ == 0)
201 initialization_complete = true;
203 if (info.device_id().empty() || bsd_name->empty()) {
204 if (initialization_complete)
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());
220 if (update_type == UPDATE_DEVICE_REMOVED) {
221 if (it != disk_info_map_.end())
222 disk_info_map_.erase(it);
224 disk_info_map_[*bsd_name] = info;
225 if (ShouldPostNotificationForDisk(info))
226 receiver()->ProcessAttach(info);
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)
235 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
236 StorageInfo* device_info) const {
239 if (!path.IsAbsolute())
242 base::FilePath current = path;
243 const base::FilePath root(base::FilePath::kSeparators);
244 while (current != root) {
246 if (FindDiskWithMountPoint(current, &info)) {
250 current = current.DirName();
256 void StorageMonitorMac::EjectDevice(
257 const std::string& device_id,
258 base::Callback<void(EjectStatus)> callback) {
259 StorageInfo::Type type;
261 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
262 callback.Run(EJECT_FAILURE);
266 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
267 image_capture_device_manager_.get()) {
268 image_capture_device_manager_->EjectDevice(uuid, callback);
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);
282 if (bsd_name.empty()) {
283 callback.Run(EJECT_NO_SUCH_DEVICE);
287 receiver()->ProcessDetach(device_id);
289 base::ScopedCFTypeRef<DADiskRef> disk(
290 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
292 callback.Run(StorageMonitor::EJECT_FAILURE);
295 // Get the reference to the full disk for ejecting.
296 disk.reset(DADiskCopyWholeDisk(disk));
298 callback.Run(StorageMonitor::EJECT_FAILURE);
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));
311 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
312 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
313 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
317 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
318 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
319 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
323 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
326 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
327 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
330 void StorageMonitorMac::GetDiskInfoAndUpdate(
332 StorageMonitorMac::UpdateType update_type) {
333 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
335 pending_disk_updates_++;
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)));
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());
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()) {
370 StorageMonitor* StorageMonitor::CreateInternal() {
371 return new StorageMonitorMac();
374 } // namespace storage_monitor