Introduction
This article describes the procedures and issues involved in the development of software applications to communicate with the Media Changer and to obtain basic information about its internal structure.
Discovering Changer Name
The first operation that should be done is to create OS handle to Media changer. Handle can be obtained by CreateFile
API.
[DllImport("kernel32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern SafeFileHandle CreateFile(
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
As you can see, the first parameter is file name. Most attached hardware devices get unique system names, a.k.a. DOS name. Media changer can get the following system names: MediaChangeX
, ChangerX
. To obtain the exact name, we can call the Win API method:
[DllImport("kernel32.dll")]
static extern uint QueryDosDevice(string lpDeviceName,
IntPtr lpTargetPath, uint ucchMax);
private static string QueryDosDevice()
{
IntPtr mem = Marshal.AllocHGlobal(1024);
try
{
if (mem == IntPtr.Zero)
throw new Win32Exception("Failed to allocate unmanaged memory");
uint returnSize = (int)QueryDosDevice(null, mem, (uint)maxSize);
if (returnSize == 0)
throw new Win32Exception("Failed to obtain DOS devices info");
string allDevices = Marshal.PtrToStringAnsi(mem, returnSize);
return allDevices;
}
finally
{
if(mem != IntPtr.Zero )
Marshal.FreeHGlobal(mem);
}
}
The returned string
will contain all system DOS names. Search for Changer
or MediaChanger
words. You will find the exact name for your Media Changer. If you have two or more Media Changers attached to your machine, you can use DeviceIOControl
to obtain address information, such as the target ID (TID) and the logical unit number (LUN) of a particular SCSI target. So you will get the information whose DOS name is allocated for device that you are working with.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
uint dwIoControlCode,
IntPtr lpInBuffer,
uint nInBufferSize,
IntPtr lpOutBuffer,
uint nOutBufferSize,
out uint lpBytesReturned,
IntPtr lpOverlapped);
private static string MapDosName(string fileHandle)
{
IntPtr ptr = Marshal.AllocHGlobal(1024);
try
{
bool status = SCSI.DeviceIoControl(
fileHandle,
SCSI.IOCTL_SCSI_GET_ADDRESS,
IntPtr.Zero,
0,
ptr,
1024,
out returned,
IntPtr.Zero);
if (status == false)
{
return string.Format("Error opening the DEVICE {0}",
Marshal.GetLastWin32Error());
}
return string.Format("Length={0}, PortNumber={1}, PathId={2},
TargetId={3}, Lun={4}",
Marshal.ReadInt32(ptr, 0),
Marshal.ReadByte(ptr, 4),
Marshal.ReadByte(ptr, 5),
Marshal.ReadByte(ptr, 6),
Marshal.ReadByte(ptr, 7));
}
finally
{
Marshal.Release(ptr);
}
}
Discovering Changer Structure
Now, we have enough information to work with Media Changer Device, so we will focus on the API function that causes a SCSI BUS Changer device to return information about itself. For example, some devices can have the following internal structure (logical):
As you remember, magnetic tape data storage is an integration of two separate devices, a tape drive and a media changer. The media changer consists of all the mechanics and electronics required to store and move tape cartridges while the tape drive provides the read/write functionality. So when tape is inserted to the devices it goes to the Mail Slot. From mail slot, it can be moved to regular slot (slots 1-8) or Drive. When the tape is in Drive, it is possible to perform IO operations. Let’s see how discovering can be done by C# code. One of the possible ways is performing IOCTL_CHANGER_GET_ELEMENT_STATUS
call. See Discover
method, this method will type status for each existing regular slot and for the drive.
public Changer.ChangerElementStatusEx
GetElementStatus(uint address, Changer.ElementType elementType)
{
IntPtr ptrIn = IntPtr.Zero;
IntPtr ptrOut = IntPtr.Zero;
try
{
m_Log.Debug("ENTER GetElementStatus");
Changer.ChangerReadElementStatus readStatusIn =
new Changer.ChangerReadElementStatus();
readStatusIn.ElementList.NumberOfElements = 1;
readStatusIn.ElementList.Element.ElementAddress = address;
readStatusIn.ElementList.Element.ElementType = elementType;
readStatusIn.VolumeTagInfo = 1;
ptrIn = Marshal.AllocHGlobal(Marshal.SizeOf(readStatusIn));
Marshal.StructureToPtr(readStatusIn, ptrIn, true);
Changer.ChangerElementStatusEx driveStatus =
new Changer.ChangerElementStatusEx();
ptrOut = Marshal.AllocHGlobal(Marshal.SizeOf(driveStatus));
uint readBytes;
if (false == Changer.DeviceIoControl(
ChangerHandle, (uint)Changer.IOCTL_CHANGER_GET_ELEMENT_STATUS, ptrIn, (uint)Marshal.SizeOf(readStatusIn), ptrOut, (uint)Marshal.SizeOf(driveStatus), out readBytes, IntPtr.Zero ))
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, "Cannot access device, error:" + error);
}
driveStatus = (Changer.ChangerElementStatusEx)Marshal.PtrToStructure
(ptrOut, driveStatus.GetType());
return driveStatus;
}
catch (Exception e)
{
m_Log.Error("GetElementStatus", e);
throw;
}
finally
{
if (ptrIn != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrIn);
}
if (ptrOut != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrOut);
}
m_Log.Debug("EXIT GetElementStatus");
}
}
public void Discover()
{
int errorCode = 0;
Changer.ChangerElementStatusEx status;
for(int i = 0; i++; i < int.MaxValue)
{
try
{
status = GetElementStatus((uint)i, Changer.ElementType.ChangerSlot);
m_Log.InfoFormat("Regular slot {0} is {1} empty",
i, status.IsEmpty ? "" : "not");
}
catch (Win32Exception e)
{
if (e.NativeErrorCode ==
ERROR_ILLEGAL_ELEMENT_ADDRESS) break;
throw;
}
}
status = m_TapeOperator.GetElementStatus(0, Changer.ElementType.ChangerDrive);
m_Log.InfoFormat("Driver is {0} empty", status.IsEmpty ? "" : "not");
}
Conclusion
This article covered Status functionality of Media Changers. When status of Media Changer is obtained, we can perform MOVE
, EJECT
, INSERT
and IO
functionality to perform pretty good backup system.
For IO
functionality, you can see Magnetic tape data storage. Part 1.
MOVE
, EJECT
and INSERT
will be covered in the next article.
History
- 28th October, 2010: Initial post