using System; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; namespace MaksIT.LTO.Core; public partial class TapeDeviceHandler : IDisposable { #region Tape IOCTL Commands from ntddtape.h // // NtDeviceIoControlFile IoControlCode values for this device. // // Warning: Remember that the low two bits of the code specify how the // buffers are passed to the driver! // private const uint IOCTL_TAPE_ERASE = (FILE_DEVICE_TAPE << 16) | ((FILE_READ_ACCESS | FILE_WRITE_ACCESS) << 14) | (0x0000 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_PREPARE = (FILE_DEVICE_TAPE << 16) | ((FILE_READ_ACCESS) << 14) | (0x0001 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_WRITE_MARKS = (FILE_DEVICE_TAPE << 16) | ((FILE_READ_ACCESS | FILE_WRITE_ACCESS) << 14) | (0x0002 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_GET_POSITION = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0003 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_SET_POSITION = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0004 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_GET_DRIVE_PARAMS = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0005 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_SET_DRIVE_PARAMS = (FILE_DEVICE_TAPE << 16) | ((FILE_READ_ACCESS | FILE_WRITE_ACCESS) << 14) | (0x0006 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_GET_MEDIA_PARAMS = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0007 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_SET_MEDIA_PARAMS = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0008 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_GET_STATUS = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0009 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_CREATE_PARTITION = (FILE_DEVICE_TAPE << 16) | ((FILE_READ_ACCESS | FILE_WRITE_ACCESS) << 14) | (0x000A << 2) | METHOD_BUFFERED; // // The following device control codes are common for all class drivers. The // functions codes defined here must match all of the other class drivers. // // Warning: these codes will be replaced in the future with the IOCTL_STORAGE // codes included below // private const uint IOCTL_TAPE_MEDIA_REMOVAL = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0201 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_EJECT_MEDIA = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0202 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_LOAD_MEDIA = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0203 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_RESERVE = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0204 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_RELEASE = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0205 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_CHECK_VERIFY = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0200 << 2) | METHOD_BUFFERED; private const uint IOCTL_TAPE_FIND_NEW_DEVICES = (FILE_DEVICE_TAPE << 16) | (FILE_READ_ACCESS << 14) | (0x0206 << 2) | METHOD_BUFFERED; #endregion #region Tape IOCTL Structures from ntddtape.h // // IOCTL_TAPE_ERASE definitions // public const uint TAPE_ERASE_SHORT = 0; public const uint TAPE_ERASE_LONG = 1; [StructLayout(LayoutKind.Sequential)] public struct TAPE_ERASE { public uint Type; // ULONG in C is equivalent to uint in C# public byte Immediate; // BOOLEAN in C is typically a byte in C# } // // IOCTL_TAPE_PREPARE definitions // public const uint TAPE_LOAD = 0; public const uint TAPE_UNLOAD = 1; public const uint TAPE_TENSION = 2; public const uint TAPE_LOCK = 3; public const uint TAPE_UNLOCK = 4; public const uint TAPE_FORMAT = 5; [StructLayout(LayoutKind.Sequential)] public struct TAPE_PREPARE { public uint Operation; // ULONG in C is equivalent to uint in C# public byte Immediate; // BOOLEAN in C is typically a byte in C# } // // IOCTL_TAPE_WRITE_MARKS definitions // public const uint TAPE_SETMARKS = 0; public const uint TAPE_FILEMARKS = 1; public const uint TAPE_SHORT_FILEMARKS = 2; public const uint TAPE_LONG_FILEMARKS = 3; [StructLayout(LayoutKind.Sequential)] public struct TAPE_WRITE_MARKS { public uint Type; // ULONG in C is equivalent to uint in C# public uint Count; // ULONG in C is equivalent to uint in C# public byte Immediate; // BOOLEAN in C is typically a byte in C# } // // IOCTL_TAPE_SET_POSITION definitions // public const uint TAPE_ABSOLUTE_POSITION = 0; public const uint TAPE_LOGICAL_POSITION = 1; public const uint TAPE_PSEUDO_LOGICAL_POSITION = 2; [StructLayout(LayoutKind.Sequential)] public struct TAPE_GET_POSITION { public uint Type; public uint Partition; public uint OffsetLow; public uint OffsetHigh; } // // IOCTL_TAPE_SET_POSITION definitions // public const uint TAPE_REWIND = 0; public const uint TAPE_ABSOLUTE_BLOCK = 1; public const uint TAPE_LOGICAL_BLOCK = 2; public const uint TAPE_PSEUDO_LOGICAL_BLOCK = 3; public const uint TAPE_SPACE_END_OF_DATA = 4; public const uint TAPE_SPACE_RELATIVE_BLOCKS = 5; public const uint TAPE_SPACE_FILEMARKS = 6; public const uint TAPE_SPACE_SEQUENTIAL_FMKS = 7; public const uint TAPE_SPACE_SETMARKS = 8; public const uint TAPE_SPACE_SEQUENTIAL_SMKS = 9; [StructLayout(LayoutKind.Sequential)] public struct TAPE_SET_POSITION { public uint Method; public uint Partition; public long Offset; public byte Immediate; } // // IOCTL_TAPE_GET_DRIVE_PARAMS definitions // // // Definitions for FeaturesLow parameter // public const uint TAPE_DRIVE_FIXED = 0x00000001; public const uint TAPE_DRIVE_SELECT = 0x00000002; public const uint TAPE_DRIVE_INITIATOR = 0x00000004; public const uint TAPE_DRIVE_ERASE_SHORT = 0x00000010; public const uint TAPE_DRIVE_ERASE_LONG = 0x00000020; public const uint TAPE_DRIVE_ERASE_BOP_ONLY = 0x00000040; public const uint TAPE_DRIVE_ERASE_IMMEDIATE = 0x00000080; public const uint TAPE_DRIVE_TAPE_CAPACITY = 0x00000100; public const uint TAPE_DRIVE_TAPE_REMAINING = 0x00000200; public const uint TAPE_DRIVE_FIXED_BLOCK = 0x00000400; public const uint TAPE_DRIVE_VARIABLE_BLOCK = 0x00000800; public const uint TAPE_DRIVE_WRITE_PROTECT = 0x00001000; public const uint TAPE_DRIVE_EOT_WZ_SIZE = 0x00002000; public const uint TAPE_DRIVE_ECC = 0x00010000; public const uint TAPE_DRIVE_COMPRESSION = 0x00020000; public const uint TAPE_DRIVE_PADDING = 0x00040000; public const uint TAPE_DRIVE_REPORT_SMKS = 0x00080000; public const uint TAPE_DRIVE_GET_ABSOLUTE_BLK = 0x00100000; public const uint TAPE_DRIVE_GET_LOGICAL_BLK = 0x00200000; public const uint TAPE_DRIVE_SET_EOT_WZ_SIZE = 0x00400000; public const uint TAPE_DRIVE_EJECT_MEDIA = 0x01000000; //don't use this bit! // //can't be a low features bit! // //reserved; high features only // // Definitions for FeaturesHigh parameter // public const uint TAPE_DRIVE_LOAD_UNLOAD = 0x80000001; public const uint TAPE_DRIVE_TENSION = 0x80000002; public const uint TAPE_DRIVE_LOCK_UNLOCK = 0x80000004; public const uint TAPE_DRIVE_REWIND_IMMEDIATE = 0x80000008; public const uint TAPE_DRIVE_SET_BLOCK_SIZE = 0x80000010; public const uint TAPE_DRIVE_LOAD_UNLD_IMMED = 0x80000040; public const uint TAPE_DRIVE_TENSION_IMMED = 0x80000080; public const uint TAPE_DRIVE_LOCK_UNLK_IMMED = 0x80000100; public const uint TAPE_DRIVE_SET_ECC = 0x80000200; public const uint TAPE_DRIVE_SET_COMPRESSION = 0x80000400; public const uint TAPE_DRIVE_SET_PADDING = 0x80000800; public const uint TAPE_DRIVE_SET_REPORT_SMKS = 0x80001000; public const uint TAPE_DRIVE_ABSOLUTE_BLK = 0x80002000; public const uint TAPE_DRIVE_ABS_BLK_IMMED = 0x80004000; public const uint TAPE_DRIVE_LOGICAL_BLK = 0x80008000; public const uint TAPE_DRIVE_LOG_BLK_IMMED = 0x80010000; public const uint TAPE_DRIVE_END_OF_DATA = 0x80020000; public const uint TAPE_DRIVE_RELATIVE_BLKS = 0x80040000; public const uint TAPE_DRIVE_FILEMARKS = 0x80080000; public const uint TAPE_DRIVE_SEQUENTIAL_FMKS = 0x80100000; public const uint TAPE_DRIVE_SETMARKS = 0x80200000; public const uint TAPE_DRIVE_SEQUENTIAL_SMKS = 0x80400000; public const uint TAPE_DRIVE_REVERSE_POSITION = 0x80800000; public const uint TAPE_DRIVE_SPACE_IMMEDIATE = 0x81000000; public const uint TAPE_DRIVE_WRITE_SETMARKS = 0x82000000; public const uint TAPE_DRIVE_WRITE_FILEMARKS = 0x84000000; public const uint TAPE_DRIVE_WRITE_SHORT_FMKS = 0x88000000; public const uint TAPE_DRIVE_WRITE_LONG_FMKS = 0x90000000; public const uint TAPE_DRIVE_WRITE_MARK_IMMED = 0xA0000000; public const uint TAPE_DRIVE_FORMAT = 0xC0000000; public const uint TAPE_DRIVE_FORMAT_IMMEDIATE = 0x80000000; public const uint TAPE_DRIVE_HIGH_FEATURES = 0x80000000; //mask for high features flag [StructLayout(LayoutKind.Sequential)] public struct TAPE_GET_DRIVE_PARAMETERS { public uint ECC; public uint Compression; public uint DataPadding; public uint ReportSetmarks; public uint DefaultBlockSize; public uint MaximumBlockSize; public uint MinimumBlockSize; public uint MaximumPartitionCount; public uint FeaturesLow; public uint FeaturesHigh; public uint EOTWarningZoneSize; } // // IOCTL_TAPE_SET_DRIVE_PARAMETERS definitions // [StructLayout(LayoutKind.Sequential)] public struct TAPE_SET_DRIVE_PARAMETERS { public uint ECC; public uint Compression; public uint DataPadding; public uint ReportSetmarks; public uint EOTWarningZoneSize; } // // IOCTL_TAPE_GET_MEDIA_PARAMETERS definitions // [StructLayout(LayoutKind.Sequential)] public struct TAPE_GET_MEDIA_PARAMETERS { public uint Capacity; public uint Remaining; public uint BlockSize; public uint PartitionCount; public byte WriteProtected; } // // IOCTL_TAPE_SET_MEDIA_PARAMETERS definitions // [StructLayout(LayoutKind.Sequential)] public struct TAPE_SET_MEDIA_PARAMETERS { public uint BlockSize; } // // IOCTL_TAPE_CREATE_PARTITION definitions // public const uint TAPE_FIXED_PARTITIONS = 0; public const uint TAPE_SELECT_PARTITIONS = 1; public const uint TAPE_INITIATOR_PARTITIONS = 2; [StructLayout(LayoutKind.Sequential)] public struct TAPE_CREATE_PARTITION { public uint Method; public uint Count; public uint Size; } // // WMI Methods // public const uint TAPE_QUERY_DRIVE_PARAMETERS = 0; public const uint TAPE_QUERY_MEDIA_CAPACITY = 1; public const uint TAPE_CHECK_FOR_DRIVE_PROBLEM = 2; public const uint TAPE_QUERY_IO_ERROR_DATA = 3; public const uint TAPE_QUERY_DEVICE_ERROR_DATA = 4; [StructLayout(LayoutKind.Sequential)] public struct TAPE_WMI_OPERATIONS { public uint Method; public uint DataBufferSize; public IntPtr DataBuffer; } // // Type of drive errors // public enum TAPE_DRIVE_PROBLEM_TYPE { TapeDriveProblemNone, TapeDriveReadWriteWarning, TapeDriveReadWriteError, TapeDriveReadWarning, TapeDriveWriteWarning, TapeDriveReadError, TapeDriveWriteError, TapeDriveHardwareError, TapeDriveUnsupportedMedia, TapeDriveScsiConnectionError, TapeDriveTimetoClean, TapeDriveCleanDriveNow, TapeDriveMediaLifeExpired, TapeDriveSnappedTape } #endregion #region Tape IOCTL Methods from ntddtape.h /// /// Erase the tape /// /// The type of erase operation. Valid values are and . public int Erase(uint type) { TAPE_ERASE erase = new TAPE_ERASE { Type = type, Immediate = 0 }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(erase)); try { Marshal.StructureToPtr(erase, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_ERASE, inBuffer, (uint)Marshal.SizeOf(erase), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine($"Erase Tape ({type}): Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Erase Tape: Failed with error code {error}"); return error; } } finally { Marshal.FreeHGlobal(inBuffer); } return 0; } /// /// Prepare the tape for a specific operation /// /// The type of prepare operation. Valid values are , , and . public void Prepare(uint operation) { TAPE_PREPARE prepare = new TAPE_PREPARE { Operation = operation, Immediate = 0 }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(prepare)); try { Marshal.StructureToPtr(prepare, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_PREPARE, inBuffer, (uint)Marshal.SizeOf(prepare), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine($"Prepare Tape ({operation}): Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Prepare Tape: Failed with error code {error}"); } } finally { Marshal.FreeHGlobal(inBuffer); } } /// /// Write tape marks /// The type of marks to write. Valid values are , , and . /// The number of marks to write. public void WriteMarks(uint type, uint count) { TAPE_WRITE_MARKS marks = new TAPE_WRITE_MARKS { Type = type, Count = count, Immediate = 0 }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(marks)); try { Marshal.StructureToPtr(marks, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_WRITE_MARKS, inBuffer, (uint)Marshal.SizeOf(marks), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine("Write Marks: Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Write Marks: Failed with error code {error}"); } } finally { Marshal.FreeHGlobal(inBuffer); } } public class TapePosition { public uint? MethodType { get; set; } public uint? Partition { get; set; } public uint? OffsetLow { get; set; } public uint? OffsetHigh { get; set; } public int? Error { get; set; } } /// /// Get the current tape position /// /// The type of position to get. Valid values are , and . /// The partition number. /// The low offset value. /// The high offset value. /// The tape position . public TapePosition GetPosition(uint type, uint partition = 0, uint offsetLow = 0, uint offsetHigh = 0) { TAPE_GET_POSITION position = new TAPE_GET_POSITION { Type = 0, Partition = partition, OffsetLow = offsetLow, OffsetHigh = offsetHigh }; IntPtr outBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(position)); try { bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_GET_POSITION, IntPtr.Zero, 0, outBuffer, (uint)Marshal.SizeOf(position), out uint bytesReturned, IntPtr.Zero); if (result) { position = Marshal.PtrToStructure(outBuffer); Console.WriteLine("Get Position: Success"); Console.WriteLine($"Type: {position.Type}"); Console.WriteLine($"Partition: {position.Partition}"); Console.WriteLine($"OffsetLow: {position.OffsetLow}"); Console.WriteLine($"OffsetHigh: {position.OffsetHigh}"); return new TapePosition { MethodType = position.Type, Partition = position.Partition, OffsetLow = position.OffsetLow, OffsetHigh = position.OffsetHigh }; } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Get Position: Failed with error code {error}"); return new TapePosition { Error = error }; } } finally { Marshal.FreeHGlobal(outBuffer); } } /// /// Set the tape position /// /// The method to use for setting the position. Valid values are , , , , , , , , and . /// The partition number. /// The offset value. public void SetPosition(uint method, uint partition = 0, long offset = 0) { TAPE_SET_POSITION position = new TAPE_SET_POSITION { Method = method, Partition = partition, Offset = offset, Immediate = 0 }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(position)); try { Marshal.StructureToPtr(position, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_SET_POSITION, inBuffer, (uint)Marshal.SizeOf(position), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine($"Set Position ({method}): Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Set Position ({method}): Failed with error code {error}"); } } finally { Marshal.FreeHGlobal(inBuffer); } } /// /// Get the drive parameters /// /// The maximum block size supported by the drive. /// The minimum block size supported by the drive. public void GetDriveParameters(out uint minBlockSize, out uint maxBlockSize) { TAPE_GET_DRIVE_PARAMETERS driveParams = new TAPE_GET_DRIVE_PARAMETERS { ECC = 0, Compression = 0, DataPadding = 0, ReportSetmarks = 0, DefaultBlockSize = 0, MaximumBlockSize = 0, MinimumBlockSize = 0, MaximumPartitionCount = 0, FeaturesLow = 0, FeaturesHigh = 0, EOTWarningZoneSize = 0 }; IntPtr outBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(driveParams)); try { bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_GET_DRIVE_PARAMS, IntPtr.Zero, 0, outBuffer, (uint)Marshal.SizeOf(driveParams), out uint bytesReturned, IntPtr.Zero); if (result) { driveParams = Marshal.PtrToStructure(outBuffer); minBlockSize = driveParams.MinimumBlockSize; maxBlockSize = driveParams.MaximumBlockSize; Console.WriteLine($"Drive Parameters: MinBlockSize = {minBlockSize}, MaxBlockSize = {maxBlockSize}"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Get Drive Parameters Failed with error code {error}"); minBlockSize = 0; maxBlockSize = 0; } } finally { Marshal.FreeHGlobal(outBuffer); } } /// /// Set the drive parameters /// /// The error correction code (ECC) setting. /// The compression setting. /// The data padding setting. /// The report setmarks setting. /// The end-of-tape (EOT) warning zone size setting. public void SetDriveParams(uint ecc, uint compression = 0, uint dataPadding = 0, uint reportSetmarks = 0, uint eotWarningZoneSize = 0) { TAPE_SET_DRIVE_PARAMETERS driveParams = new TAPE_SET_DRIVE_PARAMETERS { ECC = ecc, Compression = compression, DataPadding = dataPadding, ReportSetmarks = reportSetmarks, EOTWarningZoneSize = eotWarningZoneSize }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(driveParams)); try { Marshal.StructureToPtr(driveParams, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_SET_DRIVE_PARAMS, inBuffer, (uint)Marshal.SizeOf(driveParams), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine("Set Drive Params: Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Set Drive Params Failed with error code {error}"); } } finally { Marshal.FreeHGlobal(inBuffer); } } public void GetMediaParams(uint capacity = 0, uint remaining = 0, uint blockSize = 0, uint partitionCount = 0, byte writeProtected = 0) { TAPE_GET_MEDIA_PARAMETERS mediaParams = new TAPE_GET_MEDIA_PARAMETERS { Capacity = 0, Remaining = 0, BlockSize = 0, PartitionCount = 0, WriteProtected = 0 }; IntPtr outBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(mediaParams)); try { bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_GET_MEDIA_PARAMS, IntPtr.Zero, 0, outBuffer, (uint)Marshal.SizeOf(mediaParams), out uint bytesReturned, IntPtr.Zero); if (result) { mediaParams = Marshal.PtrToStructure(outBuffer); Console.WriteLine("Get Media Params: Success"); Console.WriteLine($"Capacity: {mediaParams.Capacity}"); Console.WriteLine($"Remaining: {mediaParams.Remaining}"); Console.WriteLine($"BlockSize: {mediaParams.BlockSize}"); Console.WriteLine($"PartitionCount: {mediaParams.PartitionCount}"); Console.WriteLine($"WriteProtected: {mediaParams.WriteProtected}"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Get Media Params Failed with error code {error}"); } } finally { Marshal.FreeHGlobal(outBuffer); } } public void SetMediaParams(uint blockSize) { TAPE_SET_MEDIA_PARAMETERS mediaParams = new TAPE_SET_MEDIA_PARAMETERS { BlockSize = blockSize }; IntPtr inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(mediaParams)); try { Marshal.StructureToPtr(mediaParams, inBuffer, false); bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_SET_MEDIA_PARAMS, inBuffer, (uint)Marshal.SizeOf(mediaParams), IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (!result) { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Set Block Size Failed with error code {error}"); } else { Console.WriteLine($"Set Block Size ({blockSize}): Success"); } } finally { Marshal.FreeHGlobal(inBuffer); } } public int GetStatus() { bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_GET_STATUS, IntPtr.Zero, 0, IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (result) { Console.WriteLine("Get Status: Success"); } else { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Get Status: Failed with error code {error}"); return error; } return 0; } public void CreatePartition(uint method = 0, uint count = 0, uint size = 0) { TAPE_CREATE_PARTITION partition = new TAPE_CREATE_PARTITION { Method = 0, Count = 1, Size = 0 }; bool result = DeviceIoControl(_tapeHandle, IOCTL_TAPE_CREATE_PARTITION, IntPtr.Zero, 0, IntPtr.Zero, 0, out uint bytesReturned, IntPtr.Zero); if (!result) { int error = Marshal.GetLastWin32Error(); Console.WriteLine($"Create Partition: Failed with error code {error}"); } else { Console.WriteLine("Create Partition: Success"); } } #endregion }