storage_monitor_mac.mm 12 KB

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