VirtualSCSITarget.cs 23 KB

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