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/bind.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" 22 23namespace storage_monitor { 24 25namespace { 26 27const char kDiskImageModelName[] = "Disk Image"; 28 29base::string16 GetUTF16FromDictionary(CFDictionaryRef dictionary, 30 CFStringRef key) { 31 CFStringRef value = 32 base::mac::GetValueFromDictionary<CFStringRef>(dictionary, key); 33 if (!value) 34 return base::string16(); 35 return base::SysCFStringRefToUTF16(value); 36} 37 38base::string16 JoinName(const base::string16& name, 39 const base::string16& addition) { 40 if (addition.empty()) 41 return name; 42 if (name.empty()) 43 return addition; 44 return name + static_cast<base::char16>(' ') + addition; 45} 46 47StorageInfo::Type GetDeviceType(bool is_removable, bool has_dcim) { 48 if (!is_removable) 49 return StorageInfo::FIXED_MASS_STORAGE; 50 if (has_dcim) 51 return StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM; 52 return StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM; 53} 54 55StorageInfo BuildStorageInfo( 56 CFDictionaryRef dict, std::string* bsd_name) { 57 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, 58 base::BlockingType::MAY_BLOCK); 59 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); 64 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; 73 if (size_number) 74 CFNumberGetValue(size_number, kCFNumberLongLongType, &size_in_bytes); 75 76 base::string16 vendor = GetUTF16FromDictionary( 77 dict, kDADiskDescriptionDeviceVendorKey); 78 base::string16 model = GetUTF16FromDictionary( 79 dict, kDADiskDescriptionDeviceModelKey); 80 base::string16 label = GetUTF16FromDictionary( 81 dict, kDADiskDescriptionVolumeNameKey); 82 83 CFUUIDRef uuid = base::mac::GetValueFromDictionary<CFUUIDRef>( 84 dict, kDADiskDescriptionVolumeUUIDKey); 85 std::string unique_id; 86 if (uuid) { 87 base::ScopedCFTypeRef<CFStringRef> uuid_string( 88 CFUUIDCreateString(NULL, uuid)); 89 if (uuid_string.get()) 90 unique_id = base::SysCFStringRefToUTF8(uuid_string); 91 } 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); 99 } 100 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); 111 112 return StorageInfo(device_id, location.value(), label, vendor, model, 113 size_in_bytes); 114} 115 116struct EjectDiskOptions { 117 std::string bsd_name; 118 base::OnceCallback<void(StorageMonitor::EjectStatus)> callback; 119 base::ScopedCFTypeRef<DADiskRef> disk; 120}; 121 122void PostEjectCallback(DADiskRef disk, 123 DADissenterRef dissenter, 124 void* context) { 125 std::unique_ptr<EjectDiskOptions> options_deleter( 126 static_cast<EjectDiskOptions*>(context)); 127 if (dissenter) { 128 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE); 129 return; 130 } 131 132 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_OK); 133} 134 135void PostUnmountCallback(DADiskRef disk, 136 DADissenterRef dissenter, 137 void* context) { 138 std::unique_ptr<EjectDiskOptions> options_deleter( 139 static_cast<EjectDiskOptions*>(context)); 140 if (dissenter) { 141 std::move(options_deleter->callback).Run(StorageMonitor::EJECT_IN_USE); 142 return; 143 } 144 145 DADiskEject(options_deleter->disk.get(), kDADiskEjectOptionDefault, 146 PostEjectCallback, options_deleter.release()); 147} 148 149void EjectDisk(EjectDiskOptions* options) { 150 DADiskUnmount(options->disk.get(), kDADiskUnmountOptionWhole, 151 PostUnmountCallback, options); 152} 153 154} // namespace 155 156StorageMonitorMac::StorageMonitorMac() : pending_disk_updates_(0) { 157} 158 159StorageMonitorMac::~StorageMonitorMac() { 160 if (session_.get()) { 161 DASessionUnscheduleFromRunLoop( 162 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 163 } 164} 165 166void StorageMonitorMac::Init() { 167 session_.reset(DASessionCreate(NULL)); 168 169 // Register for callbacks for attached, changed, and removed devices. 170 // This will send notifications for existing devices too. 171 DARegisterDiskAppearedCallback( 172 session_, 173 kDADiskDescriptionMatchVolumeMountable, 174 DiskAppearedCallback, 175 this); 176 DARegisterDiskDisappearedCallback( 177 session_, 178 kDADiskDescriptionMatchVolumeMountable, 179 DiskDisappearedCallback, 180 this); 181 DARegisterDiskDescriptionChangedCallback( 182 session_, 183 kDADiskDescriptionMatchVolumeMountable, 184 kDADiskDescriptionWatchVolumePath, 185 DiskDescriptionChangedCallback, 186 this); 187 188 DASessionScheduleWithRunLoop( 189 session_, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); 190 191 image_capture_device_manager_.reset(new ImageCaptureDeviceManager); 192 image_capture_device_manager_->SetNotifications(receiver()); 193} 194 195void StorageMonitorMac::UpdateDisk(UpdateType update_type, 196 std::string* bsd_name, 197 const StorageInfo& info) { 198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 199 DCHECK(bsd_name); 200 201 pending_disk_updates_--; 202 bool initialization_complete = false; 203 if (!IsInitialized() && pending_disk_updates_ == 0) 204 initialization_complete = true; 205 206 if (info.device_id().empty() || bsd_name->empty()) { 207 if (initialization_complete) 208 MarkInitialized(); 209 return; 210 } 211 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()); 220 } 221 } 222 223 if (update_type == UPDATE_DEVICE_REMOVED) { 224 if (it != disk_info_map_.end()) 225 disk_info_map_.erase(it); 226 } else { 227 disk_info_map_[*bsd_name] = info; 228 if (ShouldPostNotificationForDisk(info)) 229 receiver()->ProcessAttach(info); 230 } 231 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) 235 MarkInitialized(); 236} 237 238bool StorageMonitorMac::GetStorageInfoForPath(const base::FilePath& path, 239 StorageInfo* device_info) const { 240 DCHECK(device_info); 241 242 if (!path.IsAbsolute()) 243 return false; 244 245 base::FilePath current = path; 246 const base::FilePath root(base::FilePath::kSeparators); 247 while (current != root) { 248 StorageInfo info; 249 if (FindDiskWithMountPoint(current, &info)) { 250 *device_info = info; 251 return true; 252 } 253 current = current.DirName(); 254 } 255 256 return false; 257} 258 259void StorageMonitorMac::EjectDevice( 260 const std::string& device_id, 261 base::OnceCallback<void(EjectStatus)> callback) { 262 StorageInfo::Type type; 263 std::string uuid; 264 if (!StorageInfo::CrackDeviceId(device_id, &type, &uuid)) { 265 std::move(callback).Run(EJECT_FAILURE); 266 return; 267 } 268 269 if (type == StorageInfo::MAC_IMAGE_CAPTURE && 270 image_capture_device_manager_.get()) { 271 image_capture_device_manager_->EjectDevice(uuid, std::move(callback)); 272 return; 273 } 274 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); 281 break; 282 } 283 } 284 285 if (bsd_name.empty()) { 286 std::move(callback).Run(EJECT_NO_SUCH_DEVICE); 287 return; 288 } 289 290 receiver()->ProcessDetach(device_id); 291 292 base::ScopedCFTypeRef<DADiskRef> disk( 293 DADiskCreateFromBSDName(NULL, session_, bsd_name.c_str())); 294 if (!disk.get()) { 295 std::move(callback).Run(StorageMonitor::EJECT_FAILURE); 296 return; 297 } 298 // Get the reference to the full disk for ejecting. 299 disk.reset(DADiskCopyWholeDisk(disk)); 300 if (!disk.get()) { 301 std::move(callback).Run(StorageMonitor::EJECT_FAILURE); 302 return; 303 } 304 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)); 311} 312 313// static 314void StorageMonitorMac::DiskAppearedCallback(DADiskRef disk, void* context) { 315 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context); 316 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_ADDED); 317} 318 319// static 320void StorageMonitorMac::DiskDisappearedCallback(DADiskRef disk, void* context) { 321 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context); 322 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_REMOVED); 323} 324 325// static 326void StorageMonitorMac::DiskDescriptionChangedCallback(DADiskRef disk, 327 CFArrayRef keys, 328 void *context) { 329 StorageMonitorMac* monitor = static_cast<StorageMonitorMac*>(context); 330 monitor->GetDiskInfoAndUpdate(disk, UPDATE_DEVICE_CHANGED); 331} 332 333void StorageMonitorMac::GetDiskInfoAndUpdate( 334 DADiskRef disk, 335 StorageMonitorMac::UpdateType update_type) { 336 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 337 338 pending_disk_updates_++; 339 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))); 347} 348 349 350bool 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()); 358} 359 360bool 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()) { 366 *info = it->second; 367 return true; 368 } 369 } 370 return false; 371} 372 373StorageMonitor* StorageMonitor::CreateInternal() { 374 return new StorageMonitorMac(); 375} 376 377} // namespace storage_monitor 378