VirtualSCSITarget.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* Copyright (C) 2012-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 System.Runtime.InteropServices;
  12. using DiskAccessLibrary;
  13. using Utilities;
  14. namespace SCSI
  15. {
  16. public class VirtualSCSITarget : SCSITarget
  17. {
  18. private List<Disk> m_disks;
  19. private object m_ioLock = new object(); // "In multithreaded applications, a stream must be accessed in a thread-safe way"
  20. public event EventHandler<LogEntry> OnLogEntry;
  21. public VirtualSCSITarget(List<Disk> disks)
  22. {
  23. m_disks = disks;
  24. }
  25. public override SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
  26. {
  27. SCSICommandDescriptorBlock command;
  28. try
  29. {
  30. command = SCSICommandDescriptorBlock.FromBytes(commandBytes, 0);
  31. }
  32. catch(UnsupportedSCSICommandException)
  33. {
  34. Log(Severity.Error, "Unsupported SCSI Command (0x{0})", commandBytes[0].ToString("X"));
  35. response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  36. return SCSIStatusCodeName.CheckCondition;
  37. }
  38. return ExecuteCommand(command, lun, data, out response);
  39. }
  40. public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  41. {
  42. Log(Severity.Verbose, "Executing command: {0}", command.OpCode);
  43. if (command.OpCode == SCSIOpCodeName.ReportLUNs)
  44. {
  45. uint allocationLength = command.TransferLength;
  46. return ReportLUNs(allocationLength, out response);
  47. }
  48. else if (lun >= m_disks.Count)
  49. {
  50. Log(Severity.Error, "LUN {0}: Invalid LUN", lun);
  51. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
  52. return SCSIStatusCodeName.CheckCondition;
  53. }
  54. else if (command.OpCode == SCSIOpCodeName.TestUnitReady)
  55. {
  56. return TestUnitReady(lun, out response);
  57. }
  58. else if (command.OpCode == SCSIOpCodeName.RequestSense)
  59. {
  60. uint allocationLength = command.TransferLength;
  61. return RequestSense(lun, allocationLength, out response);
  62. }
  63. else if (command.OpCode == SCSIOpCodeName.Inquiry)
  64. {
  65. return Inquiry((InquiryCommand)command, lun, out response);
  66. }
  67. else if (command.OpCode == SCSIOpCodeName.Reserve6)
  68. {
  69. return Reserve6(lun, out response);
  70. }
  71. else if (command.OpCode == SCSIOpCodeName.Release6)
  72. {
  73. return Release6(lun, out response);
  74. }
  75. else if (command.OpCode == SCSIOpCodeName.ModeSense6)
  76. {
  77. return ModeSense6((ModeSense6CommandDescriptorBlock)command, lun, out response);
  78. }
  79. else if (command.OpCode == SCSIOpCodeName.ReadCapacity10)
  80. {
  81. return ReadCapacity10(lun, out response);
  82. }
  83. else if (command.OpCode == SCSIOpCodeName.Read6 ||
  84. command.OpCode == SCSIOpCodeName.Read10 ||
  85. command.OpCode == SCSIOpCodeName.Read16)
  86. {
  87. return Read(command, lun, out response);
  88. }
  89. else if (command.OpCode == SCSIOpCodeName.Write6 ||
  90. command.OpCode == SCSIOpCodeName.Write10 ||
  91. command.OpCode == SCSIOpCodeName.Write16)
  92. {
  93. return Write(command, lun, data, out response);
  94. }
  95. else if (command.OpCode == SCSIOpCodeName.Verify10 ||
  96. command.OpCode == SCSIOpCodeName.Verify16)
  97. {
  98. return Verify(lun, out response);
  99. }
  100. else if (command.OpCode == SCSIOpCodeName.SynchronizeCache10)
  101. {
  102. return SynchronizeCache10(lun, out response);
  103. }
  104. else if (command.OpCode == SCSIOpCodeName.ServiceActionIn &&
  105. command.ServiceAction == ServiceAction.ReadCapacity16)
  106. {
  107. uint allocationLength = command.TransferLength;
  108. return ReadCapacity16(lun, allocationLength, out response);
  109. }
  110. else
  111. {
  112. Log(Severity.Error, "Unsupported SCSI Command (0x{0})", command.OpCode.ToString("X"));
  113. response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
  114. return SCSIStatusCodeName.CheckCondition;
  115. }
  116. }
  117. public SCSIStatusCodeName Inquiry(InquiryCommand command, LUNStructure lun, out byte[] response)
  118. {
  119. if (!command.EVPD)
  120. {
  121. if ((int)command.PageCode == 0)
  122. {
  123. StandardInquiryData inquiryData = new StandardInquiryData();
  124. inquiryData.PeripheralDeviceType = 0; // Direct access block device
  125. inquiryData.VendorIdentification = "TalAloni";
  126. inquiryData.ProductIdentification = "SCSI Disk " + ((ushort)lun).ToString();
  127. inquiryData.ProductRevisionLevel = "1.00";
  128. inquiryData.DriveSerialNumber = 0;
  129. inquiryData.CmdQue = true;
  130. inquiryData.Version = 5; // SPC-3
  131. NotifyStandardInquiry(this, new StandardInquiryEventArgs(lun, inquiryData));
  132. response = inquiryData.GetBytes();
  133. }
  134. else
  135. {
  136. Log(Severity.Error, "Inquiry: Invalid request");
  137. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  138. return SCSIStatusCodeName.CheckCondition;
  139. }
  140. }
  141. else
  142. {
  143. Log(Severity.Verbose, "Inquiry: Page code: 0x{0}", command.PageCode.ToString("X"));
  144. switch (command.PageCode)
  145. {
  146. case VitalProductDataPageName.SupportedVPDPages:
  147. {
  148. SupportedVitaLProductDataPages page = new SupportedVitaLProductDataPages();
  149. page.SupportedPageList.Add((byte)VitalProductDataPageName.SupportedVPDPages);
  150. page.SupportedPageList.Add((byte)VitalProductDataPageName.UnitSerialNumber);
  151. page.SupportedPageList.Add((byte)VitalProductDataPageName.DeviceIdentification);
  152. response = page.GetBytes();
  153. break;
  154. }
  155. case VitalProductDataPageName.UnitSerialNumber:
  156. {
  157. UnitSerialNumberVPDPage page = new UnitSerialNumberVPDPage();
  158. // Older products that only support the Product Serial Number parameter will have a page length of 08h, while newer products that support both parameters (Vendor Unique field from the StandardInquiryData) will have a page length of 14h
  159. // Microsoft iSCSI Target uses values such as "34E5A6FC-3ACC-452D-AEDA-6EE2EFF20FB4"
  160. ulong serialNumber = 0;
  161. page.ProductSerialNumber = serialNumber.ToString("00000000");
  162. response = page.GetBytes();
  163. break;
  164. }
  165. case VitalProductDataPageName.DeviceIdentification:
  166. {
  167. DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
  168. // NAA identifier is needed to prevent 0xF4 BSOD during Windows setup
  169. page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetNAAExtendedIdentifier(5, lun));
  170. NotifyDeviceIdentificationInquiry(this, new DeviceIdentificationInquiryEventArgs(lun, page));
  171. response = page.GetBytes();
  172. break;
  173. }
  174. case VitalProductDataPageName.BlockLimits:
  175. {
  176. /* Provide only when requeste explicitly */
  177. BlockLimitsVPDPage page = new BlockLimitsVPDPage();
  178. page.OptimalTransferLengthGranularity = 128;
  179. page.MaximumTransferLength = (uint)DiskAccessLibrary.Settings.MaximumTransferSizeLBA;
  180. page.OptimalTransferLength = 128;
  181. response = page.GetBytes();
  182. break;
  183. }
  184. case VitalProductDataPageName.BlockDeviceCharacteristics:
  185. {
  186. /* Provide only when requeste explicitly */
  187. BlockDeviceCharacteristicsVPDPage page = new BlockDeviceCharacteristicsVPDPage();
  188. page.MediumRotationRate = 0; // Not reported
  189. response = page.GetBytes();
  190. break;
  191. }
  192. default:
  193. {
  194. Log(Severity.Error, "Inquiry: Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
  195. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  196. return SCSIStatusCodeName.CheckCondition;
  197. }
  198. }
  199. }
  200. // we must not return more bytes than InquiryCommand.AllocationLength
  201. if (response.Length > command.AllocationLength)
  202. {
  203. response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
  204. }
  205. return SCSIStatusCodeName.Good;
  206. }
  207. public SCSIStatusCodeName ModeSense6(ModeSense6CommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  208. {
  209. Log(Severity.Verbose, "ModeSense6: Page code: 0x{0}, Sub page code: 0x{1}", command.PageCode.ToString("X"), command.SubpageCode.ToString("X"));
  210. byte[] pageData;
  211. switch ((ModePageCodeName)command.PageCode)
  212. {
  213. case ModePageCodeName.CachingParametersPage:
  214. {
  215. CachingParametersPage page = new CachingParametersPage();
  216. page.RCD = true;
  217. pageData = page.GetBytes();
  218. break;
  219. }
  220. case ModePageCodeName.ControlModePage:
  221. {
  222. ControlModePage page = new ControlModePage();
  223. pageData = page.GetBytes();
  224. break;
  225. }
  226. case ModePageCodeName.PowerConditionModePage:
  227. {
  228. if (command.SubpageCode == 0x00)
  229. {
  230. PowerConditionModePage page = new PowerConditionModePage();
  231. pageData = page.GetBytes();
  232. break;
  233. }
  234. else if (command.SubpageCode == 0x01)
  235. {
  236. PowerConsumptionModePage page = new PowerConsumptionModePage();
  237. pageData = page.GetBytes();
  238. break;
  239. }
  240. else
  241. {
  242. Log(Severity.Error, "ModeSense6: Power condition subpage 0x{0} is not implemented", command.SubpageCode.ToString("x"));
  243. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  244. return SCSIStatusCodeName.CheckCondition;
  245. }
  246. }
  247. case ModePageCodeName.InformationalExceptionsControlModePage:
  248. {
  249. InformationalExceptionsControlModePage page = new InformationalExceptionsControlModePage();
  250. pageData = page.GetBytes();
  251. break;
  252. }
  253. case ModePageCodeName.ReturnAllPages:
  254. {
  255. CachingParametersPage page1 = new CachingParametersPage();
  256. page1.RCD = true;
  257. InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();
  258. pageData = new byte[page1.Length + page2.Length];
  259. Array.Copy(page1.GetBytes(), pageData, page1.Length);
  260. Array.Copy(page2.GetBytes(), 0, pageData, page1.Length, page2.Length);
  261. break;
  262. }
  263. case ModePageCodeName.VendorSpecificPage:
  264. {
  265. // Windows 2000 will request this page, we immitate Microsoft iSCSI Target by sending back an empty page
  266. VendorSpecificPage page = new VendorSpecificPage();
  267. pageData = page.GetBytes();
  268. break;
  269. }
  270. default:
  271. {
  272. Log(Severity.Error, "ModeSense6: ModeSense6 page 0x{0} is not implemented", command.PageCode.ToString("x"));
  273. response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
  274. return SCSIStatusCodeName.CheckCondition;
  275. }
  276. }
  277. ModeParameterHeader6 header = new ModeParameterHeader6();
  278. header.WP = m_disks[lun].IsReadOnly; // Write protected, even when set to true, Windows does not always prevent the disk from being written to.
  279. header.DPOFUA = true; // Microsoft iSCSI Target support this
  280. byte[] descriptorBytes = new byte[0];
  281. if (!command.DBD)
  282. {
  283. ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
  284. descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
  285. descriptorBytes = descriptor.GetBytes();
  286. }
  287. header.BlockDescriptorLength = (byte)descriptorBytes.Length;
  288. header.ModeDataLength += (byte)(descriptorBytes.Length + pageData.Length);
  289. response = new byte[1 + header.ModeDataLength];
  290. Array.Copy(header.GetBytes(), 0, response, 0, header.Length);
  291. Array.Copy(descriptorBytes, 0, response, header.Length, descriptorBytes.Length);
  292. Array.Copy(pageData, 0, response, header.Length + descriptorBytes.Length, pageData.Length);
  293. // we must not return more bytes than ModeSense6Command.AllocationLength
  294. if (response.Length > command.AllocationLength)
  295. {
  296. response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
  297. }
  298. return SCSIStatusCodeName.Good;
  299. }
  300. public SCSIStatusCodeName ReadCapacity10(LUNStructure lun, out byte[] response)
  301. {
  302. ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  303. response = parameter.GetBytes();
  304. return SCSIStatusCodeName.Good;
  305. }
  306. public SCSIStatusCodeName ReadCapacity16(LUNStructure lun, uint allocationLength, out byte[] response)
  307. {
  308. ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(m_disks[lun].Size, (uint)m_disks[lun].BytesPerSector);
  309. response = parameter.GetBytes();
  310. // we must not return more bytes than ReadCapacity16.AllocationLength
  311. if (response.Length > allocationLength)
  312. {
  313. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  314. }
  315. return SCSIStatusCodeName.Good;
  316. }
  317. public SCSIStatusCodeName ReportLUNs(uint allocationLength, out byte[] response)
  318. {
  319. ReportLUNsParameter parameter = new ReportLUNsParameter(m_disks.Count);
  320. response = parameter.GetBytes();
  321. // we must not return more bytes than ReportLUNs.AllocationLength
  322. if (response.Length > allocationLength)
  323. {
  324. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  325. }
  326. return SCSIStatusCodeName.Good;
  327. }
  328. public SCSIStatusCodeName Read(SCSICommandDescriptorBlock command, LUNStructure lun, out byte[] response)
  329. {
  330. Disk disk = m_disks[lun];
  331. int sectorCount = (int)command.TransferLength;
  332. Log(Severity.Verbose, "LUN {0}: Reading {1} blocks starting from LBA {2}", lun, sectorCount, (long)command.LogicalBlockAddress64);
  333. try
  334. {
  335. lock (m_ioLock)
  336. {
  337. response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
  338. }
  339. return SCSIStatusCodeName.Good;
  340. }
  341. catch (ArgumentOutOfRangeException)
  342. {
  343. Log(Severity.Error, "Read error: LBA out of range");
  344. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  345. return SCSIStatusCodeName.CheckCondition;
  346. }
  347. catch (IOException ex)
  348. {
  349. int error = Marshal.GetHRForException(ex);
  350. if (error == (int)Win32Error.ERROR_CRC)
  351. {
  352. Log(Severity.Error, "Read error: CRC error");
  353. response = FormatSenseData(SenseDataParameter.GetWriteFaultSenseData());
  354. return SCSIStatusCodeName.CheckCondition;
  355. }
  356. else
  357. {
  358. Log(Severity.Error, "Read error: {0}", ex.ToString());
  359. response = FormatSenseData(SenseDataParameter.GetMediumErrorUnrecoverableReadErrorSenseData());
  360. return SCSIStatusCodeName.CheckCondition;
  361. }
  362. }
  363. }
  364. // Some initiators (i.e. EFI iSCSI DXE) will send 'Request Sense' upon connection (likely just to verify the medium is ready)
  365. public SCSIStatusCodeName RequestSense(LUNStructure lun, uint allocationLength, out byte[] response)
  366. {
  367. response = FormatSenseData(SenseDataParameter.GetNoSenseSenseData());
  368. // we must not return more bytes than RequestSense.AllocationLength
  369. if (response.Length > allocationLength)
  370. {
  371. response = ByteReader.ReadBytes(response, 0, (int)allocationLength);
  372. }
  373. return SCSIStatusCodeName.Good;
  374. }
  375. public SCSIStatusCodeName Reserve6(LUNStructure lun, out byte[] response)
  376. {
  377. response = new byte[0];
  378. return SCSIStatusCodeName.Good;
  379. }
  380. public SCSIStatusCodeName SynchronizeCache10(LUNStructure lun, out byte[] response)
  381. {
  382. response = new byte[0];
  383. return SCSIStatusCodeName.Good;
  384. }
  385. public SCSIStatusCodeName Release6(LUNStructure lun, out byte[] response)
  386. {
  387. response = new byte[0];
  388. return SCSIStatusCodeName.Good;
  389. }
  390. public SCSIStatusCodeName TestUnitReady(LUNStructure lun, out byte[] response)
  391. {
  392. response = new byte[0];
  393. return SCSIStatusCodeName.Good;
  394. }
  395. public SCSIStatusCodeName Verify(LUNStructure lun, out byte[] response)
  396. {
  397. response = new byte[0];
  398. return SCSIStatusCodeName.Good;
  399. }
  400. public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
  401. {
  402. Disk disk = m_disks[lun];
  403. if (disk.IsReadOnly)
  404. {
  405. Log(Severity.Verbose, "LUN {0}: Refused attempt to write to a read-only disk", lun);
  406. SenseDataParameter senseData = SenseDataParameter.GetDataProtectSenseData();
  407. response = senseData.GetBytes();
  408. return SCSIStatusCodeName.CheckCondition;
  409. }
  410. Log(Severity.Verbose, "LUN {0}: Writing {1} blocks starting from LBA {2}", lun, command.TransferLength, (long)command.LogicalBlockAddress64);
  411. try
  412. {
  413. lock (m_ioLock)
  414. {
  415. disk.WriteSectors((long)command.LogicalBlockAddress64, data);
  416. }
  417. response = new byte[0];
  418. return SCSIStatusCodeName.Good;
  419. }
  420. catch (ArgumentOutOfRangeException)
  421. {
  422. Log(Severity.Error, "Write error: LBA out of range");
  423. response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
  424. return SCSIStatusCodeName.CheckCondition;
  425. }
  426. catch (IOException ex)
  427. {
  428. Log(Severity.Error, "Write error: {0}", ex.ToString());
  429. response = FormatSenseData(SenseDataParameter.GetMediumErrorWriteFaultSenseData());
  430. return SCSIStatusCodeName.CheckCondition;
  431. }
  432. }
  433. public void Log(Severity severity, string message)
  434. {
  435. // To be thread-safe we must capture the delegate reference first
  436. EventHandler<LogEntry> handler = OnLogEntry;
  437. if (handler != null)
  438. {
  439. handler(this, new LogEntry(DateTime.Now, severity, "Virtual SCSI Target", message));
  440. }
  441. }
  442. public void Log(Severity severity, string message, params object[] args)
  443. {
  444. Log(severity, String.Format(message, args));
  445. }
  446. public List<Disk> Disks
  447. {
  448. get
  449. {
  450. return m_disks;
  451. }
  452. }
  453. public static byte[] FormatSenseData(SenseDataParameter senseData)
  454. {
  455. byte[] senseDataBytes = senseData.GetBytes();
  456. byte[] result = new byte[senseDataBytes.Length + 2];
  457. Array.Copy(BigEndianConverter.GetBytes((ushort)senseDataBytes.Length), 0, result, 0, 2);
  458. Array.Copy(senseDataBytes, 0, result, 2, senseDataBytes.Length);
  459. return result;
  460. }
  461. }
  462. }