RawDiskImage.cs 9.5 KB

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