SampleProgram.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /**
  2. * @file Program.cs
  3. *
  4. * @copyright 2018-2019 Bill Zissimopoulos
  5. */
  6. /*
  7. * This file is part of WinSpd.
  8. *
  9. * You can redistribute it and/or modify it under the terms of the GNU
  10. * General Public License version 3 as published by the Free Software
  11. * Foundation.
  12. *
  13. * Licensees holding a valid commercial license may use this software
  14. * in accordance with the commercial license agreement provided in
  15. * conjunction with the software. The terms and conditions of any such
  16. * commercial license agreement shall govern, supersede, and render
  17. * ineffective any application of the GPLv3 license to this software,
  18. * notwithstanding of any reference thereto in the software or
  19. * associated repository.
  20. */
  21. using System;
  22. using System.IO;
  23. using System.Runtime.InteropServices;
  24. using Spd;
  25. using StorageUnitStatus = Spd.Interop.StorageUnitStatus;
  26. using UnmapDescriptor = Spd.Interop.UnmapDescriptor;
  27. using Partition = Spd.Interop.Partition;
  28. namespace rawdisk
  29. {
  30. class RawDisk : StorageUnitBase
  31. {
  32. public const UInt32 MaxTransferLength = 64 * 1024;
  33. public RawDisk(String RawDiskFile,
  34. UInt64 BlockCount, UInt32 BlockLength)
  35. {
  36. _BlockCount = BlockCount;
  37. _BlockLength = BlockLength;
  38. if (RawDiskFile.StartsWith("\\\\?\\"))
  39. RawDiskFile = RawDiskFile.Substring(4);
  40. _Stream = new FileStream(RawDiskFile,
  41. FileMode.OpenOrCreate, FileAccess.ReadWrite, 0, (int)MaxTransferLength, false);
  42. FILE_SET_SPARSE_BUFFER Sparse;
  43. UInt32 BytesTransferred;
  44. Sparse.SetSparse = true;
  45. _Sparse = DeviceIoControl(_Stream.SafeFileHandle.DangerousGetHandle(),
  46. 0x900c4/*FSCTL_SET_SPARSE*/,
  47. ref Sparse, (UInt32)Marshal.SizeOf(Sparse),
  48. IntPtr.Zero, 0, out BytesTransferred, IntPtr.Zero);
  49. UInt64 FileSize = (UInt64)_Stream.Length;
  50. bool ZeroSize = 0 == FileSize;
  51. if (ZeroSize)
  52. FileSize = BlockCount * BlockLength;
  53. if (0 == FileSize || BlockCount * BlockLength != FileSize)
  54. throw new ArgumentException();
  55. _Stream.SetLength((long)FileSize);
  56. if (ZeroSize)
  57. {
  58. Partition[] Partitions = new Partition[1];
  59. Byte[] Buffer = new Byte[512];
  60. Partitions[0].Type = 7;
  61. Partitions[0].BlockAddress = 4096 >= BlockLength ? 4096 / BlockLength : 1;
  62. Partitions[0].BlockCount = BlockCount - Partitions[0].BlockAddress;
  63. if (0 == StorageUnitHost.SpdDefinePartitionTable(Partitions, Buffer))
  64. {
  65. _Stream.Position = 0;
  66. _Stream.Write(Buffer, 0, Buffer.Length);
  67. _Stream.Flush();
  68. }
  69. }
  70. }
  71. public override void Init(Object Host0)
  72. {
  73. StorageUnitHost Host = (StorageUnitHost)Host0;
  74. Host.Guid = Guid.NewGuid();
  75. Host.BlockCount = _BlockCount;
  76. Host.BlockLength = _BlockLength;
  77. Host.MaxTransferLength = MaxTransferLength;
  78. }
  79. public override void Stopped(Object Host0)
  80. {
  81. _Stream.Dispose();
  82. }
  83. public override void Read(Byte[] Buffer, UInt64 BlockAddress, UInt32 BlockCount, Boolean Flush,
  84. ref StorageUnitStatus Status)
  85. {
  86. if (Flush)
  87. this.Flush(BlockAddress, BlockCount, ref Status);
  88. lock (this) /* I want pread */
  89. {
  90. _Stream.Position = (long)(BlockAddress * _BlockLength);
  91. _Stream.Read(Buffer, 0, (int)(BlockCount * _BlockLength));
  92. /* FIX: we assume that we are reading from a file and ignore the return value */
  93. }
  94. }
  95. public override void Write(Byte[] Buffer, UInt64 BlockAddress, UInt32 BlockCount, Boolean Flush,
  96. ref StorageUnitStatus Status)
  97. {
  98. lock (this) /* I want pwrite */
  99. {
  100. _Stream.Position = (long)(BlockAddress * _BlockLength);
  101. _Stream.Write(Buffer, 0, (int)(BlockCount * _BlockLength));
  102. }
  103. if (Flush)
  104. this.Flush(BlockAddress, BlockCount, ref Status);
  105. }
  106. public override void Flush(UInt64 BlockAddress, UInt32 BlockCount,
  107. ref StorageUnitStatus Status)
  108. {
  109. _Stream.Flush();
  110. }
  111. public override void Unmap(UnmapDescriptor[] Descriptors,
  112. ref StorageUnitStatus Status)
  113. {
  114. FILE_ZERO_DATA_INFORMATION Zero;
  115. UInt32 BytesTransferred;
  116. for (int I = 0; Descriptors.Length > I; I++)
  117. {
  118. Boolean SetZero = false;
  119. if (_Sparse)
  120. {
  121. Zero.FileOffset = (long)(Descriptors[I].BlockAddress * _BlockLength);
  122. Zero.BeyondFinalZero = (long)((Descriptors[I].BlockAddress + Descriptors[I].BlockCount) *
  123. _BlockLength);
  124. SetZero = DeviceIoControl(_Stream.SafeFileHandle.DangerousGetHandle(),
  125. 0x980c8/*FSCTL_SET_ZERO_DATA*/,
  126. ref Zero, (UInt32)Marshal.SizeOf(Zero),
  127. IntPtr.Zero, 0, out BytesTransferred, IntPtr.Zero);
  128. }
  129. if (!SetZero)
  130. {
  131. lock (this) /* I want pwrite */
  132. {
  133. _Stream.Position = (long)(Descriptors[I].BlockAddress * _BlockLength);
  134. int TotalLength = (int)(Descriptors[I].BlockCount * _BlockLength);
  135. Byte[] Buffer = new Byte[Math.Min(64 * 1024, TotalLength)];
  136. while (0 < TotalLength)
  137. {
  138. _Stream.Write(Buffer, 0, Buffer.Length);
  139. TotalLength -= Buffer.Length;
  140. }
  141. }
  142. }
  143. }
  144. }
  145. private UInt64 _BlockCount;
  146. private UInt32 _BlockLength;
  147. private FileStream _Stream;
  148. private Boolean _Sparse;
  149. /* interop */
  150. [StructLayout(LayoutKind.Sequential)]
  151. private struct FILE_SET_SPARSE_BUFFER
  152. {
  153. public Boolean SetSparse;
  154. }
  155. [StructLayout(LayoutKind.Sequential)]
  156. private struct FILE_ZERO_DATA_INFORMATION
  157. {
  158. public Int64 FileOffset;
  159. public Int64 BeyondFinalZero;
  160. }
  161. [DllImport("kernel32.dll", SetLastError = true)]
  162. [return: MarshalAs(UnmanagedType.U1)]
  163. private static extern Boolean DeviceIoControl(
  164. IntPtr hDevice,
  165. UInt32 dwIoControlCode,
  166. ref FILE_SET_SPARSE_BUFFER lpInBuffer,
  167. UInt32 nInBufferSize,
  168. IntPtr lpOutBuffer,
  169. UInt32 nOutBufferSize,
  170. out UInt32 lpBytesReturned,
  171. IntPtr Overlapped);
  172. [DllImport("kernel32.dll", SetLastError = true)]
  173. [return: MarshalAs(UnmanagedType.U1)]
  174. private static extern Boolean DeviceIoControl(
  175. IntPtr hDevice,
  176. UInt32 dwIoControlCode,
  177. ref FILE_ZERO_DATA_INFORMATION lpInBuffer,
  178. UInt32 nInBufferSize,
  179. IntPtr lpOutBuffer,
  180. UInt32 nOutBufferSize,
  181. out UInt32 lpBytesReturned,
  182. IntPtr Overlapped);
  183. }
  184. class Program
  185. {
  186. private class CommandLineUsageException : Exception
  187. {
  188. public CommandLineUsageException(String Message = null) : base(Message)
  189. {
  190. HasMessage = null != Message;
  191. }
  192. public bool HasMessage;
  193. }
  194. private const String PROGNAME = "rawdisk-dotnet";
  195. private static void argtos(String[] Args, ref int I, ref String V)
  196. {
  197. if (Args.Length > ++I)
  198. V = Args[I];
  199. else
  200. throw new CommandLineUsageException();
  201. }
  202. private static void argtol(String[] Args, ref int I, ref UInt32 V)
  203. {
  204. Int32 R;
  205. if (Args.Length > ++I)
  206. V = Int32.TryParse(Args[I], out R) ? (UInt32)R : V;
  207. else
  208. throw new CommandLineUsageException();
  209. }
  210. static void Main(string[] Args)
  211. {
  212. try
  213. {
  214. String RawDiskFile = null;
  215. UInt32 BlockCount = 1024 * 1024;
  216. UInt32 BlockLength = 512;
  217. String ProductId = "RawDisk-dotnet";
  218. String ProductRevision = "1.0";
  219. UInt32 WriteAllowed = 1;
  220. UInt32 CacheSupported = 1;
  221. UInt32 UnmapSupported = 1;
  222. UInt32 DebugFlags = 0;
  223. String DebugLogFile = null;
  224. String PipeName = null;
  225. StorageUnitHost Host = null;
  226. RawDisk RawDisk = null;
  227. int I;
  228. for (I = 0; Args.Length > I; I++)
  229. {
  230. String Arg = Args[I];
  231. if ('-' != Arg[0])
  232. break;
  233. switch (Arg[1])
  234. {
  235. case '?':
  236. throw new CommandLineUsageException();
  237. case 'c':
  238. argtol(Args, ref I, ref BlockCount);
  239. break;
  240. case 'C':
  241. argtol(Args, ref I, ref CacheSupported);
  242. break;
  243. case 'd':
  244. argtol(Args, ref I, ref DebugFlags);
  245. break;
  246. case 'D':
  247. argtos(Args, ref I, ref DebugLogFile);
  248. break;
  249. case 'f':
  250. argtos(Args, ref I, ref RawDiskFile);
  251. break;
  252. case 'i':
  253. argtos(Args, ref I, ref ProductId);
  254. break;
  255. case 'l':
  256. argtol(Args, ref I, ref BlockLength);
  257. break;
  258. case 'p':
  259. argtos(Args, ref I, ref PipeName);
  260. break;
  261. case 'r':
  262. argtos(Args, ref I, ref ProductRevision);
  263. break;
  264. case 'U':
  265. argtol(Args, ref I, ref UnmapSupported);
  266. break;
  267. case 'W':
  268. argtol(Args, ref I, ref WriteAllowed);
  269. break;
  270. default:
  271. throw new CommandLineUsageException();
  272. }
  273. }
  274. if (Args.Length > I)
  275. throw new CommandLineUsageException();
  276. if (null == RawDiskFile)
  277. throw new CommandLineUsageException();
  278. if (null != DebugLogFile)
  279. if (0 != StorageUnitHost.SetDebugLogFile(DebugLogFile))
  280. throw new CommandLineUsageException("cannot open debug log file");
  281. Host = new StorageUnitHost(RawDisk = new RawDisk(RawDiskFile, BlockCount, BlockLength));
  282. Host.ProductId = ProductId;
  283. Host.ProductRevisionLevel = ProductRevision;
  284. Host.WriteProtected = 0 == WriteAllowed;
  285. Host.CacheSupported = 0 != CacheSupported;
  286. Host.UnmapSupported = 0 != UnmapSupported;
  287. if (0 != Host.Start(PipeName, DebugFlags))
  288. throw new IOException("cannot start storage unit");
  289. StorageUnitHost.Log(StorageUnitHost.EVENTLOG_INFORMATION_TYPE,
  290. String.Format(
  291. "{0} -f {1} -c {2} -l {3} -i {4} -r {5} -W {6} -C {7} -U {8}{9}{10}",
  292. PROGNAME,
  293. RawDiskFile,
  294. BlockCount, BlockLength, ProductId, ProductRevision,
  295. 0 != WriteAllowed ? 1 : 0,
  296. 0 != CacheSupported ? 1 : 0,
  297. 0 != UnmapSupported ? 1 : 0,
  298. null != PipeName ? " -p " : "",
  299. null != PipeName ? PipeName : ""));
  300. Console.CancelKeyPress +=
  301. delegate (Object Sender, ConsoleCancelEventArgs Event)
  302. {
  303. Host.Shutdown();
  304. Event.Cancel = true;
  305. };
  306. Host.Wait();
  307. }
  308. catch (CommandLineUsageException ex)
  309. {
  310. StorageUnitHost.Log(StorageUnitHost.EVENTLOG_ERROR_TYPE,
  311. String.Format(
  312. "{0}" +
  313. "usage: {1} OPTIONS\n" +
  314. "\n" +
  315. "options:\n" +
  316. " -f RawDiskFile Storage unit data file\n" +
  317. " -c BlockCount Storage unit size in blocks\n" +
  318. " -l BlockLength Storage unit block length\n" +
  319. " -i ProductId 1-16 chars\n" +
  320. " -r ProductRevision 1-4 chars\n" +
  321. " -W 0|1 Disable/enable writes (deflt: enable)\n" +
  322. " -C 0|1 Disable/enable cache (deflt: enable)\n" +
  323. " -U 0|1 Disable/enable unmap (deflt: enable)\n" +
  324. " -d -1 Debug flags\n" +
  325. " -D DebugLogFile Debug log file; - for stderr\n" +
  326. " -p \\\\.\\pipe\\PipeName Listen on pipe; omit to use driver\n",
  327. ex.HasMessage ? ex.Message + "\n" : "",
  328. PROGNAME));
  329. Environment.ExitCode = 87/*ERROR_INVALID_PARAMETER*/;
  330. }
  331. catch (Exception ex)
  332. {
  333. if (ex is TypeInitializationException && null != ex.InnerException)
  334. ex = ex.InnerException;
  335. StorageUnitHost.Log(StorageUnitHost.EVENTLOG_ERROR_TYPE,
  336. ex.Message);
  337. int hr = Marshal.GetHRForException(ex);
  338. Environment.ExitCode = Marshal.GetHRForException(ex);
  339. if ((hr & 0xffff0000) == 0x80070000)
  340. Environment.ExitCode = hr & 0xffff;
  341. else
  342. Environment.ExitCode = 574/*ERROR_UNHANDLED_EXCEPTION*/;
  343. }
  344. }
  345. }
  346. }