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"
10 #include "base/mac/foundation_util.h"
11 #include "base/mac/mac_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/task/task_traits.h"
15 #include "base/task/thread_pool.h"
16 #include "base/threading/scoped_blocking_call.h"
17 #include "components/storage_monitor/image_capture_device_manager.h"
18 #include "components/storage_monitor/media_storage_util.h"
19 #include "components/storage_monitor/storage_info.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
23 namespace storage_monitor {
27 const char kDiskImageModelName[] = "Disk Image";
29 base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary,
32 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key);
34 return base::string16();
35 return base::SysCFStringRefToUTF16(value);
38 base::string16 JoinName(const base::string16& name,
39 const base::string16& addition) {
44 return name + static_cast<base::char16>(' ') + addition;
47 StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) {
49 return StorageInfo::FIXED_MASS_STORAGE;
51 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
52 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
55 StorageInfo BuildStorageInfo(
56 CFDictionaryRef dict, std::string* bsd_name) {
57 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
58 base::BlockingType::MAY_BLOCK);
60 CFStringRef device_bsd_name = base::mac::GetValueFromDictionary<CFStringRef>(
61 dict, kDADiskDescriptionMediaBSDNameKey);
62 if (device_bsd_name && bsd_name)
63 *bsd_name = base::SysCFStringRefToUTF8(device_bsd_name);
65 CFURLRef url = base::mac::GetValueFromDictionary<CFURLRef>(
66 dict, kDADiskDescriptionVolumePathKey);
67 NSURL* nsurl = base::mac::CFToNSCast(url);
68 base::FilePath location = base::mac::NSStringToFilePath([nsurl path]);
69 CFNumberRef size_number =
70 base::mac::GetValueFromDictionary<CFNumberRef>(
71 dict, kDADiskDescriptionMediaSizeKey);
72 uint64_t size_in_bytes = 0;
74 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes);
76 base::string16 vendor = GetUTF16FromDictionary(
77 dict, kDADiskDescriptionDeviceVendorKey);
78 base::string16 model = GetUTF16FromDictionary(
79 dict, kDADiskDescriptionDeviceModelKey);
80 base::string16 label = GetUTF16FromDictionary(
81 dict, kDADiskDescriptionVolumeNameKey);
83 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
84 dict, kDADiskDescriptionVolumeUUIDKey);
85 std::string unique_id;
87 base::ScopedCFTypeRef<CFStringRef> uuid_string(
88 CFUUIDCreateString(NULL, uuid));
89 if (uuid_string.get())
90 unique_id = base::SysCFStringRefToUTF8(uuid_string);
92 if (unique_id.empty()) {
93 base::string16 revision = GetUTF16FromDictionary(
94 dict, kDADiskDescriptionDeviceRevisionKey);
95 base::string16 unique_id2 = vendor;
96 unique_id2 = JoinName(unique_id2, model);
97 unique_id2 = JoinName(unique_id2, revision);
98 unique_id = base::UTF16ToUTF8(unique_id2);
101 CFBooleanRef is_removable_ref =
102 base::mac::GetValueFromDictionary<CFBooleanRef>(
103 dict, kDADiskDescriptionMediaRemovableKey);
104 bool is_removable = is_removable_ref && CFBooleanGetValue(is_removable_ref);
105 // Checking for DCIM only matters on removable devices.
106 bool has_dcim = is_removable && MediaStorageUtil::HasDcim(location);
107 StorageInfo::Type device_type = GetDeviceType(is_removable, has_dcim);
108 std::string device_id;
109 if (!unique_id.empty())
110 device_id = StorageInfo::MakeDeviceId(device_type, unique_id);
112 return StorageInfo(device_id, location.value(), label, vendor, model,
116 struct EjectDiskOptions {
117 std::string bsd_name;
118 base::OnceCallback<void(StorageMonitor::EjectStatus)> callback;
119 base::ScopedCFTypeRef<DADiskRef> disk;
122 void PostEjectCallback(DADiskRef disk,
123 DADissenterRef dissenter,
125 std::unique_ptr<EjectDiskOptions> options_deleter(
126 static_cast<EjectDiskOptions*>(context));
128 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
132 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_OK);
135 void PostUnmountCallback(DADiskRef disk,
136 DADissenterRef dissenter,
138 std::unique_ptr<EjectDiskOptions> options_deleter(
139 static_cast<EjectDiskOptions*>(context));
141 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE);
145 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault,
146 PostEjectCallback, options_deleter.release());
149 void EjectDisk(EjectDiskOptions* options) {
150 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole,
151 PostUnmountCallback, options);
156 StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) {
159 StorageMonitorMac::~StorageMonitorMac() {
160 if (session_.get()) {
161 DASessionUnscheduleFromRunLoop(
162 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
166 void StorageMonitorMac::Init() {
167 session_.reset(DASessionCreate(NULL));
169 // Register for callbacks for attached, changed, and removed devices.
170 // This will send notifications for existing devices too.
171 DARegisterDiskAppearedCallback(
173 kDADiskDescriptionMatchVolumeMountable,
174 DiskAppearedCallback,
176 DARegisterDiskDisappearedCallback(
178 kDADiskDescriptionMatchVolumeMountable,
179 DiskDisappearedCallback,
181 DARegisterDiskDescriptionChangedCallback(
183 kDADiskDescriptionMatchVolumeMountable,
184 kDADiskDescriptionWatchVolumePath,
185 DiskDescriptionChangedCallback,
188 DASessionScheduleWithRunLoop(
189 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
191 image_capture_device_manager_.reset(new ImageCaptureDeviceManager);
192 image_capture_device_manager_->SetNotifications(receiver());
195 void StorageMonitorMac::UpdateDisk(UpdateType update_type,
196 std::string* bsd_name,
197 const StorageInfo& info) {
198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
201 pending_disk_updates_--;
202 bool initialization_complete = false;
203 if (!IsInitialized() && pending_disk_updates_ == 0)
204 initialization_complete = true;
206 if (info.device_id().empty() || bsd_name->empty()) {
207 if (initialization_complete)
212 std::map<std::string, StorageInfo>::iterator it =
213 disk_info_map_.find(*bsd_name);
214 if (it != disk_info_map_.end()) {
215 // If an attached notification was previously posted then post a detached
216 // notification now. This is used for devices that are being removed or
217 // devices that have changed.
218 if (ShouldPostNotificationForDisk(it->second)) {
219 receiver()->ProcessDetach(it->second.device_id());
223 if (update_type == UPDATE_DEVICE_REMOVED) {
224 if (it != disk_info_map_.end())
225 disk_info_map_.erase(it);
227 disk_info_map_[*bsd_name] = info;
228 if (ShouldPostNotificationForDisk(info))
229 receiver()->ProcessAttach(info);
232 // We're not really honestly sure we're done, but this looks the best we
233 // can do. Any misses should go out through notifications.
234 if (initialization_complete)
238 bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path,
239 StorageInfo* device_info) const {
242 if (!path.IsAbsolute())
245 base::FilePath current = path;
246 const base::FilePath root(base::FilePath::kSeparators);
247 while (current != root) {
249 if (FindDiskWithMountPoint(current, &info)) {
253 current = current.DirName();
259 void StorageMonitorMac::EjectDevice(
260 const std::string& device_id,
261 base::OnceCallback<void(EjectStatus)> callback) {
262 StorageInfo::Type type;
264 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) {
265 std::move(callback).Run(EJECT_FAILURE);
269 if (type == StorageInfo::MAC_IMAGE_CAPTURE &&
270 image_capture_device_manager_.get()) {
271 image_capture_device_manager_->EjectDevice(uuid, std::move(callback));
275 std::string bsd_name;
276 for (std::map<std::string, StorageInfo>::iterator
277 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
278 if (it->second.device_id() == device_id) {
279 bsd_name = it->first;
280 disk_info_map_.erase(it);
285 if (bsd_name.empty()) {
286 std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
290 receiver()->ProcessDetach(device_id);
292 base::ScopedCFTypeRef<DADiskRef> disk(
293 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str()));
295 std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
298 // Get the reference to the full disk for ejecting.
299 disk.reset(DADiskCopyWholeDisk(disk));
301 std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
305 EjectDiskOptions* options = new EjectDiskOptions;
306 options->bsd_name = bsd_name;
307 options->callback = std::move(callback);
308 options->disk = std::move(disk);
309 content::GetUIThreadTaskRunner({})->PostTask(
310 FROM_HERE, base::BindOnce(EjectDisk, options));
314 void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) {
315 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
316 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED);
320 void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) {
321 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
322 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED);
326 void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk,
329 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context);
330 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED);
333 void StorageMonitorMac::GetDiskInfoAndUpdate(
335 StorageMonitorMac::UpdateType update_type) {
336 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
338 pending_disk_updates_++;
340 base::ScopedCFTypeRef<CFDictionaryRef> dict(DADiskCopyDescription(disk));
341 std::string* bsd_name = new std::string;
342 base::ThreadPool::PostTaskAndReplyWithResult(
343 FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
344 base::BindOnce(&BuildStorageInfo, dict, bsd_name),
345 base::BindOnce(&StorageMonitorMac::UpdateDisk, AsWeakPtr(), update_type,
346 base::Owned(bsd_name)));
350 bool StorageMonitorMac::ShouldPostNotificationForDisk(
351 const StorageInfo& info) const {
352 // Only post notifications about disks that have no empty fields and
353 // are removable. Also exclude disk images (DMGs).
354 return !info.device_id().empty() &&
355 !info.location().empty() &&
356 info.model_name() != base::ASCIIToUTF16(kDiskImageModelName) &&
357 StorageInfo::IsMassStorageDevice(info.device_id());
360 bool StorageMonitorMac::FindDiskWithMountPoint(
361 const base::FilePath& mount_point,
362 StorageInfo* info) const {
363 for (std::map<std::string, StorageInfo>::const_iterator
364 it = disk_info_map_.begin(); it != disk_info_map_.end(); ++it) {
365 if (it->second.location() == mount_point.value()) {
373 StorageMonitor* StorageMonitor::CreateInternal() {
374 return new StorageMonitorMac();
377 } // namespace storage_monitor