RawDiskImage.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /* Copyright (C) 2014-2018 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 Utilities;
  11. namespace DiskAccessLibrary
  12. {
  13. public partial class RawDiskImage : DiskImage
  14. {
  15. public const int DefaultBytesPerSector = 512;
  16. const FileOptions FILE_FLAG_NO_BUFFERING = (FileOptions)0x20000000;
  17. private bool m_isExclusiveLock;
  18. private FileStream m_stream;
  19. private int m_bytesPerSector;
  20. private long m_size;
  21. /// <exception cref="System.IO.IOException"></exception>
  22. /// <exception cref="System.UnauthorizedAccessException"></exception>
  23. public RawDiskImage(string rawDiskImagePath) : base(rawDiskImagePath)
  24. {
  25. m_bytesPerSector = DetectBytesPerSector(rawDiskImagePath);
  26. m_size = new FileInfo(rawDiskImagePath).Length;
  27. }
  28. /// <exception cref="System.IO.IOException"></exception>
  29. /// <exception cref="System.UnauthorizedAccessException"></exception>
  30. public RawDiskImage(string rawDiskImagePath, int bytesPerSector) : base(rawDiskImagePath)
  31. {
  32. m_bytesPerSector = bytesPerSector;
  33. m_size = new FileInfo(rawDiskImagePath).Length;
  34. }
  35. /// <exception cref="System.IO.IOException"></exception>
  36. public override bool ExclusiveLock()
  37. {
  38. if (!m_isExclusiveLock)
  39. {
  40. m_isExclusiveLock = true;
  41. m_stream = OpenFileStream();
  42. return true;
  43. }
  44. else
  45. {
  46. return false;
  47. }
  48. }
  49. public override bool ReleaseLock()
  50. {
  51. if (m_isExclusiveLock)
  52. {
  53. m_isExclusiveLock = false;
  54. m_stream.Close();
  55. return true;
  56. }
  57. else
  58. {
  59. return false;
  60. }
  61. }
  62. private FileStream OpenFileStream()
  63. {
  64. FileAccess fileAccess = IsReadOnly ? FileAccess.Read : FileAccess.ReadWrite;
  65. // We should use noncached I/O operations to avoid excessive RAM usage.
  66. // Note: KB99794 provides information about FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING.
  67. // We must avoid using buffered writes, using it will negatively affect the performance and reliability.
  68. // Note: once the file system write buffer is filled, Windows may delay any (buffer-dependent) pending write operations, which will create a deadlock.
  69. FileStream stream = new FileStream(this.Path, FileMode.Open, fileAccess, FileShare.Read, m_bytesPerSector, FILE_FLAG_NO_BUFFERING | FileOptions.WriteThrough);
  70. return stream;
  71. }
  72. /// <summary>
  73. /// Sector refers to physical disk sector, we can only read complete sectors
  74. /// </summary>
  75. public override byte[] ReadSectors(long sectorIndex, int sectorCount)
  76. {
  77. CheckBoundaries(sectorIndex, sectorCount);
  78. if (!m_isExclusiveLock)
  79. {
  80. m_stream = OpenFileStream();
  81. }
  82. long offset = sectorIndex * BytesPerSector;
  83. byte[] result = new byte[BytesPerSector * sectorCount];
  84. m_stream.Seek(offset, SeekOrigin.Begin);
  85. m_stream.Read(result, 0, BytesPerSector * sectorCount);
  86. if (!m_isExclusiveLock)
  87. {
  88. m_stream.Close();
  89. }
  90. return result;
  91. }
  92. public override void WriteSectors(long sectorIndex, byte[] data)
  93. {
  94. if (IsReadOnly)
  95. {
  96. throw new UnauthorizedAccessException("Attempted to perform write on a readonly disk");
  97. }
  98. CheckBoundaries(sectorIndex, data.Length / this.BytesPerSector);
  99. if (!m_isExclusiveLock)
  100. {
  101. m_stream = OpenFileStream();
  102. }
  103. long offset = sectorIndex * BytesPerSector;
  104. m_stream.Seek(offset, SeekOrigin.Begin);
  105. m_stream.Write(data, 0, data.Length);
  106. if (!m_isExclusiveLock)
  107. {
  108. m_stream.Close();
  109. }
  110. }
  111. /// <exception cref="System.IO.IOException"></exception>
  112. public override void Extend(long numberOfAdditionalBytes)
  113. {
  114. if (numberOfAdditionalBytes % this.BytesPerSector > 0)
  115. {
  116. throw new ArgumentException("numberOfAdditionalBytes must be a multiple of BytesPerSector");
  117. }
  118. #if Win32
  119. // calling AdjustTokenPrivileges and then immediately calling SetFileValidData will sometimes result in ERROR_PRIVILEGE_NOT_HELD.
  120. // We can work around the issue by obtaining the privilege before obtaining the handle.
  121. bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
  122. #endif
  123. if (!m_isExclusiveLock)
  124. {
  125. m_stream = OpenFileStream();
  126. }
  127. else
  128. {
  129. // Workaround for AdjustTokenPrivileges issue
  130. ReleaseLock();
  131. ExclusiveLock();
  132. }
  133. m_stream.SetLength(m_size + numberOfAdditionalBytes);
  134. m_size += numberOfAdditionalBytes;
  135. #if Win32
  136. if (hasManageVolumePrivilege)
  137. {
  138. try
  139. {
  140. FileStreamUtils.SetValidLength(m_stream, m_size);
  141. }
  142. catch (IOException)
  143. {
  144. }
  145. }
  146. #endif
  147. if (!m_isExclusiveLock)
  148. {
  149. m_stream.Close();
  150. }
  151. }
  152. public override int BytesPerSector
  153. {
  154. get
  155. {
  156. return m_bytesPerSector;
  157. }
  158. }
  159. public override long Size
  160. {
  161. get
  162. {
  163. return m_size;
  164. }
  165. }
  166. /// <param name="size">In bytes</param>
  167. /// <exception cref="System.IO.IOException"></exception>
  168. /// <exception cref="System.UnauthorizedAccessException"></exception>
  169. public static RawDiskImage Create(string path, long size)
  170. {
  171. int bytesPerSector = DetectBytesPerSector(path);
  172. return Create(path, size, bytesPerSector);
  173. }
  174. /// <param name="size">In bytes</param>
  175. /// <exception cref="System.IO.IOException"></exception>
  176. /// <exception cref="System.UnauthorizedAccessException"></exception>
  177. internal static RawDiskImage Create(string path, long size, int bytesPerSector)
  178. {
  179. if (size % bytesPerSector > 0)
  180. {
  181. throw new ArgumentException("size must be a multiple of bytesPerSector");
  182. }
  183. #if Win32
  184. // calling AdjustTokenPrivileges and then immediately calling SetFileValidData will sometimes result in ERROR_PRIVILEGE_NOT_HELD.
  185. // We can work around the issue by obtaining the privilege before obtaining the handle.
  186. bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
  187. #endif
  188. FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
  189. try
  190. {
  191. stream.SetLength(size);
  192. }
  193. catch (IOException)
  194. {
  195. stream.Close();
  196. try
  197. {
  198. // Delete the incomplete file
  199. File.Delete(path);
  200. }
  201. catch (IOException)
  202. {
  203. }
  204. throw;
  205. }
  206. #if Win32
  207. if (hasManageVolumePrivilege)
  208. {
  209. try
  210. {
  211. FileStreamUtils.SetValidLength(stream, size);
  212. }
  213. catch (IOException)
  214. {
  215. }
  216. }
  217. #endif
  218. stream.Close();
  219. return new RawDiskImage(path, bytesPerSector);
  220. }
  221. public static int DetectBytesPerSector(string path)
  222. {
  223. FileInfo info = new FileInfo(path);
  224. string[] components = info.Name.Split('.');
  225. if (components.Length >= 3) // file.512.img
  226. {
  227. string bytesPerSectorString = components[components.Length - 2];
  228. int bytesPerSector = Conversion.ToInt32(bytesPerSectorString, DefaultBytesPerSector);
  229. return bytesPerSector;
  230. }
  231. else
  232. {
  233. return DefaultBytesPerSector;
  234. }
  235. }
  236. }
  237. }