PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/xbmc/platform/darwin/osx/storage/OSXStorageProvider.cpp

https://gitlab.com/freesoftware/xbmc
C++ | 335 lines | 265 code | 51 blank | 19 comment | 48 complexity | 0cfcc443fbd618328cbd4ead8d672bc4 MD5 | raw file
  1. /*
  2. * Copyright (C) 2005-2018 Team Kodi
  3. * This file is part of Kodi - https://kodi.tv
  4. *
  5. * SPDX-License-Identifier: GPL-2.0-or-later
  6. * See LICENSES/README.md for more information.
  7. */
  8. #include <stdlib.h>
  9. #include "OSXStorageProvider.h"
  10. #include "utils/RegExp.h"
  11. #include "Util.h"
  12. #include "guilib/LocalizeStrings.h"
  13. #include <sys/mount.h>
  14. #include <DiskArbitration/DiskArbitration.h>
  15. #include <IOKit/storage/IOCDMedia.h>
  16. #include <IOKit/storage/IODVDMedia.h>
  17. #include "platform/darwin/osx/CocoaInterface.h"
  18. std::vector<std::pair<std::string, std::string>> COSXStorageProvider::m_mountsToNotify;
  19. std::vector<std::pair<std::string, std::string>> COSXStorageProvider::m_unmountsToNotify;
  20. std::unique_ptr<IStorageProvider> IStorageProvider::CreateInstance()
  21. {
  22. return std::make_unique<COSXStorageProvider>();
  23. }
  24. COSXStorageProvider::COSXStorageProvider()
  25. {
  26. PumpDriveChangeEvents(NULL);
  27. }
  28. void COSXStorageProvider::GetLocalDrives(VECSOURCES& localDrives)
  29. {
  30. CMediaSource share;
  31. // User home folder
  32. share.strPath = getenv("HOME");
  33. share.strName = g_localizeStrings.Get(21440);
  34. share.m_ignore = true;
  35. localDrives.push_back(share);
  36. // User desktop folder
  37. share.strPath = getenv("HOME");
  38. share.strPath += "/Desktop";
  39. share.strName = "Desktop";
  40. share.m_ignore = true;
  41. localDrives.push_back(share);
  42. // Volumes (all mounts are present here)
  43. share.strPath = "/Volumes";
  44. share.strName = "Volumes";
  45. share.m_ignore = true;
  46. localDrives.push_back(share);
  47. // This will pick up all local non-removable disks including the Root Disk.
  48. DASessionRef session = DASessionCreate(kCFAllocatorDefault);
  49. if (session)
  50. {
  51. unsigned i, count = 0;
  52. struct statfs *buf = NULL;
  53. std::string mountpoint, devicepath;
  54. count = getmntinfo(&buf, 0);
  55. for (i=0; i<count; i++)
  56. {
  57. mountpoint = buf[i].f_mntonname;
  58. devicepath = buf[i].f_mntfromname;
  59. DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, devicepath.c_str());
  60. if (disk)
  61. {
  62. CFDictionaryRef details = DADiskCopyDescription(disk);
  63. if (details)
  64. {
  65. if (kCFBooleanFalse == CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey))
  66. {
  67. CMediaSource sharesrc;
  68. sharesrc.strPath = mountpoint;
  69. Cocoa_GetVolumeNameFromMountPoint(mountpoint.c_str(), sharesrc.strName);
  70. sharesrc.m_ignore = true;
  71. localDrives.push_back(sharesrc);
  72. }
  73. CFRelease(details);
  74. }
  75. CFRelease(disk);
  76. }
  77. }
  78. CFRelease(session);
  79. }
  80. }
  81. void COSXStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives)
  82. {
  83. DASessionRef session = DASessionCreate(kCFAllocatorDefault);
  84. if (session)
  85. {
  86. unsigned i, count = 0;
  87. struct statfs *buf = NULL;
  88. std::string mountpoint, devicepath;
  89. count = getmntinfo(&buf, 0);
  90. for (i=0; i<count; i++)
  91. {
  92. mountpoint = buf[i].f_mntonname;
  93. devicepath = buf[i].f_mntfromname;
  94. DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, devicepath.c_str());
  95. if (disk)
  96. {
  97. CFDictionaryRef details = DADiskCopyDescription(disk);
  98. if (details)
  99. {
  100. if (kCFBooleanTrue == CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey))
  101. {
  102. CMediaSource share;
  103. share.strPath = mountpoint;
  104. share.m_iDriveType = CMediaSource::SOURCE_TYPE_REMOVABLE;
  105. Cocoa_GetVolumeNameFromMountPoint(mountpoint.c_str(), share.strName);
  106. share.m_ignore = true;
  107. // detect if its a cd or dvd
  108. // needs to be ejectable
  109. if (kCFBooleanTrue == CFDictionaryGetValue(details, kDADiskDescriptionMediaEjectableKey))
  110. {
  111. CFStringRef mediaKind = (CFStringRef)CFDictionaryGetValue(details, kDADiskDescriptionMediaKindKey);
  112. // and either cd or dvd kind of media in it
  113. if (mediaKind != NULL &&
  114. (CFStringCompare(mediaKind, CFSTR(kIOCDMediaClass), 0) == kCFCompareEqualTo ||
  115. CFStringCompare(mediaKind, CFSTR(kIODVDMediaClass), 0) == kCFCompareEqualTo))
  116. share.m_iDriveType = CMediaSource::SOURCE_TYPE_DVD;
  117. }
  118. removableDrives.push_back(share);
  119. }
  120. CFRelease(details);
  121. }
  122. CFRelease(disk);
  123. }
  124. }
  125. CFRelease(session);
  126. }
  127. }
  128. std::vector<std::string> COSXStorageProvider::GetDiskUsage()
  129. {
  130. std::vector<std::string> result;
  131. FILE* pipe = popen("df -HT ufs,cd9660,hfs,apfs,udf", "r");
  132. if (pipe)
  133. {
  134. char line[1024];
  135. while (fgets(line, sizeof(line) - 1, pipe))
  136. {
  137. result.emplace_back(line);
  138. }
  139. pclose(pipe);
  140. }
  141. return result;
  142. }
  143. namespace
  144. {
  145. class DAOperationContext
  146. {
  147. public:
  148. explicit DAOperationContext(const std::string& mountpath);
  149. ~DAOperationContext();
  150. DADiskRef GetDisk() const { return m_disk; }
  151. void Reset();
  152. bool WaitForCompletion(CFTimeInterval timeout);
  153. void Completed(bool success);
  154. static void CompletionCallback(DADiskRef disk, DADissenterRef dissenter, void* context);
  155. private:
  156. DAOperationContext() = delete;
  157. static void RunloopPerformCallback(void* info) {}
  158. CFRunLoopSourceContext m_runLoopSourceContext = { .perform = RunloopPerformCallback };
  159. bool m_success;
  160. bool m_completed;
  161. const DASessionRef m_session;
  162. const CFRunLoopRef m_runloop;
  163. const CFRunLoopSourceRef m_runloopSource;
  164. DADiskRef m_disk;
  165. };
  166. DAOperationContext::DAOperationContext(const std::string& mountpath)
  167. : m_success(true),
  168. m_completed(false),
  169. m_session(DASessionCreate(kCFAllocatorDefault)),
  170. m_runloop(CFRunLoopGetCurrent()), // not owner!
  171. m_runloopSource(CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &m_runLoopSourceContext))
  172. {
  173. if (m_session && m_runloop && m_runloopSource)
  174. {
  175. CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)mountpath.c_str(), mountpath.size(), TRUE);
  176. if (url)
  177. {
  178. m_disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, m_session, url);
  179. CFRelease(url);
  180. }
  181. DASessionScheduleWithRunLoop(m_session, m_runloop, kCFRunLoopDefaultMode);
  182. CFRunLoopAddSource(m_runloop, m_runloopSource, kCFRunLoopDefaultMode);
  183. }
  184. }
  185. DAOperationContext::~DAOperationContext()
  186. {
  187. if (m_session && m_runloop && m_runloopSource)
  188. {
  189. CFRunLoopRemoveSource(m_runloop, m_runloopSource, kCFRunLoopDefaultMode);
  190. DASessionUnscheduleFromRunLoop(m_session, m_runloop, kCFRunLoopDefaultMode);
  191. CFRunLoopSourceInvalidate(m_runloopSource);
  192. }
  193. if (m_disk)
  194. CFRelease(m_disk);
  195. if (m_runloopSource)
  196. CFRelease(m_runloopSource);
  197. if (m_session)
  198. CFRelease(m_session);
  199. }
  200. bool DAOperationContext::WaitForCompletion(CFTimeInterval timeout)
  201. {
  202. while (!m_completed)
  203. {
  204. if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, TRUE) == kCFRunLoopRunTimedOut)
  205. break;
  206. }
  207. return m_success;
  208. }
  209. void DAOperationContext::Completed(bool success)
  210. {
  211. m_success = success;
  212. m_completed = true;
  213. CFRunLoopSourceSignal(m_runloopSource);
  214. CFRunLoopWakeUp(m_runloop);
  215. }
  216. void DAOperationContext::Reset()
  217. {
  218. m_success = true;
  219. m_completed = false;
  220. }
  221. void DAOperationContext::CompletionCallback(DADiskRef disk, DADissenterRef dissenter, void* context)
  222. {
  223. DAOperationContext* dacontext = static_cast<DAOperationContext*>(context);
  224. bool success = true;
  225. if (dissenter)
  226. {
  227. DAReturn status = DADissenterGetStatus(dissenter);
  228. success = (status == kDAReturnSuccess || status == kDAReturnUnsupported);
  229. }
  230. dacontext->Completed(success);
  231. }
  232. } // unnamed namespace
  233. bool COSXStorageProvider::Eject(const std::string& mountpath)
  234. {
  235. if (mountpath.empty())
  236. return false;
  237. DAOperationContext ctx(mountpath);
  238. DADiskRef disk = ctx.GetDisk();
  239. if (!disk)
  240. return false;
  241. bool success = false;
  242. CFDictionaryRef details = DADiskCopyDescription(disk);
  243. if (details)
  244. {
  245. // Does the device need to be unmounted first?
  246. if (CFDictionaryGetValueIfPresent(details, kDADiskDescriptionVolumePathKey, NULL))
  247. {
  248. DADiskUnmount(disk, kDADiskUnmountOptionDefault, DAOperationContext::CompletionCallback, &ctx);
  249. success = ctx.WaitForCompletion(30.0); // timeout after 30 secs
  250. }
  251. if (success)
  252. {
  253. ctx.Reset();
  254. DADiskEject(disk, kDADiskEjectOptionDefault, DAOperationContext::CompletionCallback, &ctx);
  255. success = ctx.WaitForCompletion(30.0); // timeout after 30 secs
  256. }
  257. CFRelease(details);
  258. }
  259. return success;
  260. }
  261. bool COSXStorageProvider::PumpDriveChangeEvents(IStorageEventsCallback* callback)
  262. {
  263. // Note: If we find a way to only notify kodi user initiated mounts/unmounts we
  264. // could do this here, but currently we can't distinguish this and popups
  265. // for system initiated mounts/unmounts (like done by Time Machine automatic
  266. // backups) are very confusing and annoying.
  267. bool bChanged = !m_mountsToNotify.empty() || !m_unmountsToNotify.empty();
  268. if (bChanged)
  269. {
  270. m_mountsToNotify.clear();
  271. m_unmountsToNotify.clear();
  272. }
  273. return bChanged;
  274. }
  275. void COSXStorageProvider::VolumeMountNotification(const char* label, const char* mountpoint)
  276. {
  277. if (label && mountpoint)
  278. m_mountsToNotify.emplace_back(std::make_pair(label, mountpoint));
  279. }
  280. void COSXStorageProvider::VolumeUnmountNotification(const char* label, const char* mountpoint)
  281. {
  282. if (label && mountpoint)
  283. m_unmountsToNotify.emplace_back(std::make_pair(label, mountpoint));
  284. }