PhysicalDisk.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Runtime.InteropServices;
  11. using System.Security;
  12. using System.Text;
  13. using Microsoft.Win32.SafeHandles;
  14. using Utilities;
  15. namespace DiskAccessLibrary
  16. {
  17. public class PhysicalDisk : Disk, IDiskGeometry
  18. {
  19. // ReadFile failed with ERROR_INVALID_FUNCTION when transfer size was > 4880 sectors when working with an iSCSI drive
  20. // Note: The size of the internal buffer has no meaningful impact on performance, instead you should look at MaximumTransferSizeLBA.
  21. public const int MaximumDirectTransferSizeLBA = 2048; // 1 MB (assuming 512-byte sectors)
  22. private int m_physicalDiskIndex;
  23. private int m_bytesPerSector;
  24. private long m_size;
  25. private string m_description;
  26. private string m_serialNumber;
  27. // CHS:
  28. private long m_cylinders;
  29. private int m_tracksPerCylinder; // a.k.a. heads
  30. private int m_sectorsPerTrack;
  31. public PhysicalDisk(int physicalDiskIndex)
  32. {
  33. m_physicalDiskIndex = physicalDiskIndex;
  34. PopulateDiskInfo(); // We must do it before any read request use the disk handle
  35. PopulateDescription();
  36. }
  37. public override byte[] ReadSectors(long sectorIndex, int sectorCount)
  38. {
  39. if (sectorCount > MaximumDirectTransferSizeLBA)
  40. {
  41. // we must read one segment at the time, and copy the segments to a big bufffer
  42. byte[] buffer = new byte[sectorCount * m_bytesPerSector];
  43. for (int sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += MaximumDirectTransferSizeLBA)
  44. {
  45. int leftToRead = sectorCount - sectorOffset;
  46. int sectorsToRead = (int)Math.Min(leftToRead, MaximumDirectTransferSizeLBA);
  47. byte[] segment = ReadSectorsUnbuffered(sectorIndex + sectorOffset, sectorsToRead);
  48. Array.Copy(segment, 0, buffer, sectorOffset * m_bytesPerSector, segment.Length);
  49. }
  50. return buffer;
  51. }
  52. else
  53. {
  54. return ReadSectorsUnbuffered(sectorIndex, sectorCount);
  55. }
  56. }
  57. /// <summary>
  58. /// Sector refers to physical disk blocks, we can only read complete blocks
  59. /// </summary>
  60. public byte[] ReadSectorsUnbuffered(long sectorIndex, int sectorCount)
  61. {
  62. bool releaseHandle;
  63. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.Read, ShareMode.ReadWrite, out releaseHandle);
  64. if (!handle.IsInvalid)
  65. {
  66. FileStreamEx stream = new FileStreamEx(handle, FileAccess.Read);
  67. byte[] buffer = new byte[m_bytesPerSector * sectorCount];
  68. try
  69. {
  70. stream.Seek(sectorIndex * m_bytesPerSector, SeekOrigin.Begin);
  71. stream.Read(buffer, 0, m_bytesPerSector * sectorCount);
  72. }
  73. finally
  74. {
  75. stream.Close(releaseHandle);
  76. if (releaseHandle)
  77. {
  78. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  79. }
  80. }
  81. return buffer;
  82. }
  83. else
  84. {
  85. // we always release invalid handle
  86. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  87. // get error code and throw
  88. int errorCode = Marshal.GetLastWin32Error();
  89. string message = String.Format("Can't read sector {0} from disk {1}, Win32 Error: {2}", sectorIndex, m_physicalDiskIndex, errorCode);
  90. throw new IOException(message, errorCode);
  91. }
  92. }
  93. public override void WriteSectors(long sectorIndex, byte[] data)
  94. {
  95. if (data.Length % m_bytesPerSector > 0)
  96. {
  97. throw new IOException("Cannot write partial sectors");
  98. }
  99. int sectorCount = data.Length / m_bytesPerSector;
  100. if (sectorCount > MaximumDirectTransferSizeLBA)
  101. {
  102. // we must write one segment at the time
  103. for (int sectorOffset = 0; sectorOffset < sectorCount; sectorOffset += MaximumDirectTransferSizeLBA)
  104. {
  105. int leftToWrite = sectorCount - sectorOffset;
  106. int sectorsToWrite = (int)Math.Min(leftToWrite, MaximumDirectTransferSizeLBA);
  107. byte[] segment = new byte[sectorsToWrite * m_bytesPerSector];
  108. Array.Copy(data, sectorOffset * m_bytesPerSector, segment, 0, sectorsToWrite * m_bytesPerSector);
  109. WriteSectorsUnbuffered(sectorIndex + sectorOffset, segment);
  110. }
  111. }
  112. else
  113. {
  114. WriteSectorsUnbuffered(sectorIndex, data);
  115. }
  116. }
  117. public void WriteSectorsUnbuffered(long sectorIndex, byte[] data)
  118. {
  119. if (data.Length % m_bytesPerSector > 0)
  120. {
  121. throw new IOException("Cannot write partial sectors");
  122. }
  123. if (IsReadOnly)
  124. {
  125. throw new UnauthorizedAccessException("Attempted to perform write on a readonly disk");
  126. }
  127. bool releaseHandle;
  128. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.ReadWrite, ShareMode.Read, out releaseHandle);
  129. if (!handle.IsInvalid)
  130. {
  131. FileStreamEx stream = new FileStreamEx(handle, FileAccess.Write);
  132. try
  133. {
  134. stream.Seek(sectorIndex * m_bytesPerSector, SeekOrigin.Begin);
  135. stream.Write(data, 0, data.Length);
  136. stream.Flush();
  137. }
  138. finally
  139. {
  140. stream.Close(releaseHandle);
  141. if (releaseHandle)
  142. {
  143. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  144. }
  145. }
  146. }
  147. else
  148. {
  149. // we always release invalid handle
  150. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  151. // get error code and throw
  152. int errorCode = Marshal.GetLastWin32Error();
  153. string message = String.Format("Can't write to sector {0} of disk {1}", sectorIndex, m_physicalDiskIndex);
  154. FileStreamEx.ThrowIOError(errorCode, message);
  155. }
  156. }
  157. public bool ExclusiveLock()
  158. {
  159. bool releaseHandle;
  160. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.ReadWrite, ShareMode.Read, out releaseHandle);
  161. if (releaseHandle) // new allocation
  162. {
  163. if (!handle.IsInvalid)
  164. {
  165. return true;
  166. }
  167. else
  168. {
  169. // we always release invalid handle
  170. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  171. return false;
  172. }
  173. }
  174. else
  175. {
  176. return false;
  177. }
  178. }
  179. public bool ReleaseLock()
  180. {
  181. return PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  182. }
  183. /// <summary>
  184. /// Invalidates the cached partition table and re-enumerates the device
  185. /// </summary>
  186. /// <exception cref="System.IO.IOException"></exception>
  187. public void UpdateProperties()
  188. {
  189. bool releaseHandle;
  190. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.ReadWrite, ShareMode.Read, out releaseHandle);
  191. if (!handle.IsInvalid)
  192. {
  193. bool success = PhysicalDiskControl.UpdateDiskProperties(handle);
  194. if (!success)
  195. {
  196. throw new IOException("Failed to update disk properties");
  197. }
  198. if (releaseHandle)
  199. {
  200. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  201. }
  202. }
  203. else
  204. {
  205. // we always release invalid handle
  206. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  207. }
  208. }
  209. private void PopulateDiskInfo()
  210. {
  211. bool releaseHandle;
  212. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.Read, ShareMode.ReadWrite, out releaseHandle);
  213. if (!handle.IsInvalid)
  214. {
  215. if (!PhysicalDiskControl.IsMediaAccesible(handle))
  216. {
  217. throw new DeviceNotReadyException();
  218. }
  219. DISK_GEOMETRY diskGeometry = PhysicalDiskControl.GetDiskGeometryAndSize(handle, out m_size);
  220. if (releaseHandle)
  221. {
  222. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  223. }
  224. m_bytesPerSector = (int)diskGeometry.BytesPerSector;
  225. // CHS:
  226. m_cylinders = diskGeometry.Cylinders;
  227. m_tracksPerCylinder = (int)diskGeometry.TracksPerCylinder;
  228. m_sectorsPerTrack = (int)diskGeometry.SectorsPerTrack;
  229. }
  230. else
  231. {
  232. // we always release invalid handle
  233. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  234. // get error code and throw
  235. int errorCode = Marshal.GetLastWin32Error();
  236. string message = String.Format("Can't read from disk {0}", m_physicalDiskIndex);
  237. if (errorCode == (int)Win32Error.ERROR_FILE_NOT_FOUND)
  238. {
  239. throw new DriveNotFoundException(message);
  240. }
  241. else
  242. {
  243. FileStreamEx.ThrowIOError(errorCode, message);
  244. }
  245. }
  246. }
  247. private void PopulateDescription()
  248. {
  249. bool releaseHandle;
  250. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.Read, ShareMode.ReadWrite, out releaseHandle);
  251. if (!handle.IsInvalid)
  252. {
  253. m_description = PhysicalDiskControl.GetDeviceDescription(handle);
  254. m_serialNumber = PhysicalDiskControl.GetDeviceSerialNumber(handle);
  255. if (releaseHandle)
  256. {
  257. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  258. }
  259. }
  260. else
  261. {
  262. // we always release invalid handle
  263. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  264. // get error code and throw
  265. int errorCode = Marshal.GetLastWin32Error();
  266. string message = String.Format("Can't read from disk {0}", m_physicalDiskIndex);
  267. if (errorCode == (int)Win32Error.ERROR_FILE_NOT_FOUND)
  268. {
  269. throw new DriveNotFoundException(message);
  270. }
  271. else
  272. {
  273. FileStreamEx.ThrowIOError(errorCode, message);
  274. }
  275. }
  276. }
  277. /// <summary>
  278. /// Available on Windows Vista and newer
  279. /// </summary>
  280. public bool GetOnlineStatus()
  281. {
  282. bool isReadOnly;
  283. return GetOnlineStatus(out isReadOnly);
  284. }
  285. /// <summary>
  286. /// Available on Windows Vista and newer
  287. /// </summary>
  288. /// <exception cref="System.IO.IOException"></exception>
  289. public bool GetOnlineStatus(out bool isReadOnly)
  290. {
  291. bool releaseHandle;
  292. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.ReadWrite, ShareMode.Read, out releaseHandle);
  293. if (!handle.IsInvalid)
  294. {
  295. bool isOnline = PhysicalDiskControl.GetOnlineStatus(handle, out isReadOnly);
  296. if (releaseHandle)
  297. {
  298. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  299. }
  300. return isOnline;
  301. }
  302. else
  303. {
  304. // we always release invalid handle
  305. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  306. // get error code and throw
  307. int errorCode = Marshal.GetLastWin32Error();
  308. string message = String.Format("Can't get disk {0} online status, Win32 Error: {1}", m_physicalDiskIndex, errorCode);
  309. throw new IOException(message);
  310. }
  311. }
  312. /// <summary>
  313. /// Available on Windows Vista and newer
  314. /// </summary>
  315. public bool SetOnlineStatus(bool online)
  316. {
  317. return SetOnlineStatus(online, false);
  318. }
  319. /// <summary>
  320. /// Available on Windows Vista and newer
  321. /// </summary>
  322. /// <exception cref="System.IO.IOException"></exception>
  323. public bool SetOnlineStatus(bool online, bool persist)
  324. {
  325. bool releaseHandle;
  326. SafeFileHandle handle = PhysicalDiskHandlePool.ObtainHandle(m_physicalDiskIndex, FileAccess.ReadWrite, ShareMode.Read, out releaseHandle);
  327. if (!handle.IsInvalid)
  328. {
  329. bool success = PhysicalDiskControl.SetOnlineStatus(handle, online, persist);
  330. if (releaseHandle)
  331. {
  332. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  333. }
  334. return success;
  335. }
  336. else
  337. {
  338. // we always release invalid handle
  339. PhysicalDiskHandlePool.ReleaseHandle(m_physicalDiskIndex);
  340. int errorCode = Marshal.GetLastWin32Error();
  341. if (errorCode == (int)Win32Error.ERROR_SHARING_VIOLATION)
  342. {
  343. return false;
  344. }
  345. else
  346. {
  347. string message = String.Format("Can't take disk {0} offline, Win32 Error: {1}", m_physicalDiskIndex, errorCode);
  348. throw new IOException(message);
  349. }
  350. }
  351. }
  352. public override int BytesPerSector
  353. {
  354. get
  355. {
  356. return m_bytesPerSector;
  357. }
  358. }
  359. public override long Size
  360. {
  361. get
  362. {
  363. return m_size;
  364. }
  365. }
  366. public int PhysicalDiskIndex
  367. {
  368. get
  369. {
  370. return m_physicalDiskIndex;
  371. }
  372. }
  373. public string Description
  374. {
  375. get
  376. {
  377. return m_description;
  378. }
  379. }
  380. public string SerialNumber
  381. {
  382. get
  383. {
  384. return m_serialNumber;
  385. }
  386. }
  387. public long Cylinders
  388. {
  389. get
  390. {
  391. return m_cylinders;
  392. }
  393. }
  394. /// <summary>
  395. /// a.k.a heads
  396. /// </summary>
  397. public int TracksPerCylinder
  398. {
  399. get
  400. {
  401. return m_tracksPerCylinder;
  402. }
  403. }
  404. public int SectorsPerTrack
  405. {
  406. get
  407. {
  408. return m_sectorsPerTrack;
  409. }
  410. }
  411. }
  412. }