|
In the article, it is stated that the write operations to tape have to be in done in multiples of blocksize. However the code in the article contains:
byte[] arrayToWrite = new byte[ numberOfBlocks * BlockSize ];<br />
Array.Copy( stream, arrayToWrite, stream.Length );<br />
<br />
m_stream.Write( stream, 0, stream.Length );<br />
m_stream.Flush();
After the arrayToWrite array is correctly created and filled with the original contents of stream, arrayToWrite is not used.
Shouldn't the code be:
byte[] arrayToWrite = new byte[ numberOfBlocks * BlockSize ];<br />
Array.Copy( stream, arrayToWrite, stream.Length );<br />
<br />
m_stream.Write( arrayToWrite, 0, arrayToWrite.Length );<br />
m_stream.Flush();
The .zip file attached to the article contains the same thing.
Oz
|
|
|
|
|
Thanks Oz,
you are perfectly right.
Dima S
|
|
|
|
|
Hi..
I am presently trying to "Read/write into tape drive".
prsently i am using HP LTO Ultrium 2 drive.
In my System,tape symbolic name is same as specified "Tape0".
but i am unable to load tape using the Tape load(string tapename) function.
I havent found "using Bsw.Types.Logger;" dll to add into the program.
where can i get the dll and how can i access the tape and how can i implement it.
a small support is also a great help for me
|
|
|
|
|
Is there a way to find out the bar code of the tape?
|
|
|
|
|
Hi Venu Tam.
Yes, there is a way to read the barcode.
You can do as follows
if (false == Changer.DeviceIoControl(
ChangerHandle, // handle to device
(uint)Changer.IOCTL_CHANGER_GET_ELEMENT_STATUS, // dwIoControlCode
ptrIn, // lpInBuffer
(uint)Marshal.SizeOf(readStatusIn), // nInBufferSize
ptrOut, // output buffer
(uint)Marshal.SizeOf(driveStatus), // size of output buffer
out readBytes, // number of bytes returned
IntPtr.Zero // OVERLAPPED structure
))
The ptrOut is pointer on CHANGER_ELEMENT_STATUS_EX
(see- http://msdn.microsoft.com/en-us/library/aa363163(v=VS.85).aspx)
it contains the media barcode (Of course barcode reader should be installed on your device)
Regards,
Dima.
Dima S
|
|
|
|
|
Hi Dima,
just came across this nice piece of code some days ago. Thanks for sharing!
At the present moment I am struggeling with "m_stream.Read( buffer, 0, buffer.Length );" which gives me the "Handle does not support synchronous operations" message and " m_stream.Flush();" in method "Write", which pops up the "IO operation will not work. Most likely the file will become too long or the handle was not opened to support synchronous IO operations."-message.
Load method seems to work perfect, as all handels are created propperly.
Hope my problems do not come from my XP 64Bit installation?
Any help on this is highly appreciated.
Regards
Stocki
|
|
|
|
|
Hi Stocki,
check block size on media and block size that you are trying to read.
Regards, Dima.
Dima S
|
|
|
|
|
This code looks very useful but I'm having trouble.
I can open my device and return the blocksize (32768).
I then tried the "read" method picking a random block number of 450, but I get the error "When accessing a new tape of a multivolume partition, the current block size is incorrect".
I saw in the code that the load method seems to try one of 65535 so I changed that to match mine and still get the same (I guess it's not that as the read method seems to call the same as get default block size to work it out...)
Any ideas? In Device Manager both the tape drive blocksize and the media blocksize are both 32768.
--code:
TapeOperator _tape = new TapeOperator();
private void button1_Click(object sender, EventArgs e)
{
_tape.Load(textBox1.Text);
MessageBox.Show("Tape default blocksize is " + _tape.BlockSize.ToString());
}
private void button2_Click(object sender, EventArgs e)
{
byte[] tapeData = _tape.Read(450);
textBox2.Text = ByteArrayToString(tapeData);
_tape.Close();
}
|
|
|
|
|
Try to set the block size programmatically before you are using the device
Dima S
|
|
|
|
|
Hello,
We have a few tape programming projects, I was wondering if you'd be interested in a contracting position? Please contact me dustin@vioplex.com
|
|
|
|
|
Hi, thank you for your useful article.
I want to ask you if this software can make write/read operations also to USB TAPE DEVICES, like HP StorageWorks DAT 40 USB, not only on standard SCSI tape.
|
|
|
|
|
Hi Alberto,
you can work with USB tapes as well.
Just discover the symbolic name (DOS name) of your device
and you will be able to work with it by using ReadFile/WriteFile.
Regards, Dima.
Dima S
|
|
|
|
|
Thanks for this nice piece of code – very interesting.
I’d like to perform a similar but much simpler operation: Test if the “\\.\Tape1” tape device has already a file handler open on it. On top of that, I was wondering if it was possible using VBscript… Any suggestions would much appreciated
Cheers
|
|
|
|
|
Hi, I don't realy use VbScript so I can't give
exact solution, but I think that you should check
"VBScript and the FileSystemObject" topics in net forums.
Good luck
Dima
|
|
|
|
|
Hi, Dmitry. Thanks for this code...it´s great!
I´d like to know if you have something in C/C++ (VC6 ou VC 2005) because I´d like to make a backup tool using this language.
Alexandre
|
|
|
|
|
Hi Alexandre,
your task is much easier, because in C++ you can use
the WinAPI as is (without marshallings).
Personally I have this code only in C#. But translating it to C++ is
straight froward, just use WinAPI as is and include all need headers
as required.
Regards,
Dima S
|
|
|
|
|
"The size of .NET Boolean is 2 bytes, the size of BOOL is 4 bytes and size of BOOL is 1 byte"
And the size of BOOL is?
|
|
|
|
|
Hi, you are right
The size of BOOL is 4 bytes and the size of BOOLEAN 1 byte.
Thanks
Dima
|
|
|
|
|
I appreciate your effort.
Here is my modified code. I checked that the device is OK and I called the function Load first. but the problem still exists.
Also, I tried your original class in .Net 2.0 environment and reached the same problem.
when write and flush, the error is: "IO operation will not work. Most likely the file will become too long or the handle was not opened to support synchronous IO operations"
when read, the error is "Handle does not support synchronous operations. The parameters to the FileStream constructor may need to be changed to indicate that the handle was opened asynchronously"
i.e. when write, it need synchronous (where it is already so). when read, it needs asynchronous (which gives an error in constructor if I tried).
Also here is my consuming code
For write:
TapeOperator tape = new TapeOperator();
tape.Load(@"\\.\Tape0");
byte[] BS = new byte[n];
FileStream fs = new FileStream("c:\\Mos.txt",FileMode.Open,FileAccess.Read);
fs.Read(BS,0,(int)fs.Length);
fs.Close();
tape.Write(0, BS);
tape.Close();
For read :
TapeOperator tape = new TapeOperator();
tape.Load(@"\\.\Tape0");
byte[] BS = tape.Read(0);
FileStream fs = new FileStream("c:\\result.txt",FileMode.Open, FileAccess.Write);
fs.Write(BS,0,BS.Length);
fs.Close();
tape.Close();
The class after .Net 1.1 modifications:
using System;<br />
using System.IO;<br />
using System.Runtime.InteropServices;<br />
<br />
<br />
using System.Text;<br />
<br />
namespace Tape<br />
{<br />
#region Typedefenitions<br />
using BOOL = System.Int32;<br />
#endregion<br />
<br />
public class TapeOperator<br />
{<br />
#region Types<br />
<br />
[StructLayout(LayoutKind.Sequential)] <br />
private struct MediaInfo<br />
{<br />
public long Capacity;<br />
public long Remaining;<br />
<br />
public uint BlockSize;<br />
public uint PartitionCount;<br />
<br />
public byte IsWriteProtected;<br />
}<br />
<br />
[StructLayout( LayoutKind.Sequential )]<br />
private class DriveInfo<br />
{<br />
public byte ECC;<br />
public byte Compression;<br />
public byte DataPadding;<br />
public byte ReportSetMarks;<br />
<br />
public uint DefaultBlockSize;<br />
public uint MaximumBlockSize;<br />
public uint MinimumBlockSize;<br />
public uint PartitionCount;<br />
<br />
public uint FeaturesLow;<br />
public uint FeaturesHigh;<br />
public uint EATWarningZone;<br />
}<br />
#endregion<br />
<br />
#region Public constnts<br />
private const short FILE_ATTRIBUTE_NORMAL = 0x80;<br />
private const short INVALID_HANDLE_VALUE = -1;<br />
private const uint GENERIC_READ = 0x80000000;<br />
private const uint GENERIC_WRITE = 0x40000000;<br />
private const uint CREATE_NEW = 1;<br />
private const uint CREATE_ALWAYS = 2;<br />
private const uint OPEN_EXISTING = 3;<br />
private const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020;<br />
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;<br />
<br />
private const uint NO_ERROR = 0;<br />
private const int TAPE_LOAD = 0;<br />
private const int TAPE_UNLOAD = 1;<br />
<br />
<br />
private const int TAPE_RELATIVE_BLOCKS = 5;<br />
<br />
private const int TAPE_LOGICAL_BLOCK = 2;<br />
private const int TAPE_LOGICAL_POSITION = 1;<br />
<br />
private const int FALSE = 0;<br />
private const int TRUE = 0;<br />
<br />
private const int MEDIA_PARAMS = 0;<br />
private const int DRIVE_PARAMS = 1;<br />
#endregion<br />
<br />
#region PInvoke<br />
[DllImport( "kernel32.dll", SetLastError = true )]<br />
private static extern IntPtr CreateFile(<br />
string lpFileName,<br />
uint dwDesiredAccess,<br />
uint dwShareMode,<br />
IntPtr lpSecurityAttributes,<br />
uint dwCreationDisposition,<br />
uint dwFlagsAndAttributes,<br />
IntPtr hTemplateFile<br />
);<br />
<br />
[DllImport( "kernel32", SetLastError = true )]<br />
private static extern int PrepareTape(<br />
IntPtr handle,<br />
int prepareType,<br />
BOOL isImmediate<br />
);<br />
<br />
<br />
[DllImport( "kernel32", SetLastError = true )]<br />
private static extern int SetTapePosition(<br />
IntPtr handle,<br />
int positionType,<br />
int partition,<br />
int offsetLow,<br />
int offsetHigh,<br />
BOOL isImmediate<br />
);<br />
<br />
[DllImport( "kernel32", SetLastError = true )]<br />
private static extern int GetTapePosition(<br />
IntPtr handle,<br />
int positionType,<br />
out int partition,<br />
out int offsetLow,<br />
out int offsetHigh<br />
);<br />
<br />
[DllImport( "kernel32", SetLastError = true )]<br />
private static extern int GetTapeParameters(<br />
IntPtr handle,<br />
int operationType,<br />
ref int size,<br />
IntPtr mediaInfo<br />
);<br />
<br />
[DllImport( "kernel32", SetLastError = true )]<br />
private static extern int GetLastError();<br />
#endregion<br />
<br />
#region Private variables<br />
private FileStream m_stream;<br />
<br />
private IntPtr m_handleValue = IntPtr.Zero;<br />
private DriveInfo m_driveInfo = null; <br />
#endregion<br />
<br />
#region Public methods<br />
<br />
public void Load( string tapeName )<br />
{<br />
m_handleValue = CreateFile(<br />
tapeName,<br />
GENERIC_READ | GENERIC_WRITE,<br />
0,<br />
IntPtr.Zero,<br />
OPEN_EXISTING,<br />
FILE_ATTRIBUTE_ARCHIVE | FILE_FLAG_BACKUP_SEMANTICS,<br />
IntPtr.Zero<br />
);<br />
<br />
if ( m_handleValue == IntPtr.Zero )<br />
{<br />
throw new TapeOperatorWin32Exception(<br />
"CreateFile", Marshal.GetLastWin32Error() );<br />
}<br />
<br />
int result = PrepareTape(<br />
m_handleValue,<br />
TAPE_LOAD,<br />
TRUE<br />
);<br />
<br />
if ( result != NO_ERROR )<br />
{<br />
throw new TapeOperatorWin32Exception(<br />
"PrepareTape", Marshal.GetLastWin32Error() );<br />
}<br />
<br />
m_stream = new FileStream(<br />
m_handleValue,<br />
FileAccess.ReadWrite,<br />
true,<br />
65536,<br />
false<br />
);<br />
}<br />
<br />
public void Write( long startPos, byte[] stream )<br />
{<br />
uint numberOfBlocks = GetBlocksNumber( stream.Length );<br />
<br />
SetTapePosition( startPos );<br />
<br />
byte[] arrayToWrite = new byte[ numberOfBlocks * BlockSize ];<br />
Array.Copy( stream, arrayToWrite, stream.Length );<br />
<br />
m_stream.Write( stream, 0, stream.Length );<br />
m_stream.Flush();<br />
}<br />
<br />
public byte[] Read( long startPosition )<br />
{<br />
byte[] buffer = new byte[ BlockSize ];<br />
<br />
SetTapePosition( startPosition );<br />
<br />
m_stream.Read( buffer, 0, buffer.Length );<br />
m_stream.Flush();<br />
<br />
return buffer;<br />
}<br />
<br />
public byte[] Read( long startPosition, long bytes )<br />
{<br />
uint blocksNumber = GetBlocksNumber( bytes );<br />
int module = Convert.ToInt32( bytes % BlockSize ); <br />
<br />
byte[] buffer = new byte[ bytes ];<br />
<br />
for ( uint i = 0; i < blocksNumber; i++ )<br />
{<br />
byte[] temp = Read( i + startPosition );<br />
<br />
if ( i + 1 != blocksNumber )<br />
{<br />
Array.Copy( temp, 0, buffer, BlockSize * i, BlockSize );<br />
}<br />
else<br />
{<br />
Array.Copy( temp, 0, buffer, BlockSize * i, module ); <br />
}<br />
<br />
}
<br />
return buffer;<br />
}<br />
<br />
public bool CanRead( long startPosition )<br />
{<br />
bool status = true;<br />
long pos = GetTapePosition();<br />
<br />
try<br />
{<br />
Read( startPosition );<br />
}<br />
catch<br />
{<br />
status = false;<br />
}<br />
finally<br />
{<br />
SetTapePosition( pos );<br />
}<br />
<br />
return status;<br />
}<br />
<br />
public bool CanRead( long startPosition, long bytes )<br />
{<br />
bool status = true;<br />
long pos = GetTapePosition();<br />
<br />
try<br />
{<br />
Read( startPosition, bytes );<br />
}<br />
catch<br />
{<br />
status = false;<br />
}<br />
finally<br />
{<br />
SetTapePosition( pos );<br />
}<br />
<br />
return status;<br />
}<br />
<br />
public void Close()<br />
{<br />
if (m_handleValue != IntPtr.Zero)<br />
{<br />
m_handleValue = IntPtr.Zero;<br />
}<br />
}<br />
<br />
public void SetTapePosition( long logicalBlock )<br />
{<br />
int errorCode = 0;<br />
<br />
if ( ( errorCode = SetTapePosition(<br />
m_handleValue,<br />
TAPE_LOGICAL_BLOCK,<br />
0,<br />
( int )logicalBlock,<br />
0,<br />
TRUE ) ) != NO_ERROR )<br />
{<br />
throw new TapeOperatorWin32Exception(<br />
"SetTapePosition", Marshal.GetLastWin32Error() );<br />
}<br />
}<br />
<br />
public long GetTapePosition()<br />
{<br />
int partition;<br />
int offsetLow;<br />
int offsetHigh;<br />
<br />
if ( GetTapePosition(<br />
m_handleValue,<br />
TAPE_LOGICAL_POSITION,<br />
out partition,<br />
out offsetLow,<br />
out offsetHigh ) != NO_ERROR )<br />
{<br />
throw new TapeOperatorWin32Exception(<br />
"GetTapePosition", Marshal.GetLastWin32Error() );<br />
}<br />
<br />
long offset = ( long )( offsetHigh * Math.Pow( 2, 32 ) + offsetLow );<br />
<br />
return offset;<br />
}<br />
#endregion<br />
<br />
#region Public properties<br />
<br />
public IntPtr Handle<br />
{<br />
get<br />
{<br />
if ( m_handleValue != IntPtr.Zero )<br />
{<br />
return m_handleValue;<br />
}<br />
else<br />
{<br />
return IntPtr.Zero;<br />
}<br />
}
}<br />
<br />
public uint BlockSize<br />
{<br />
get<br />
{<br />
IntPtr ptr = IntPtr.Zero;<br />
try<br />
{<br />
if ( m_driveInfo== null )<br />
{<br />
m_driveInfo = new DriveInfo();<br />
<br />
int size = Marshal.SizeOf( m_driveInfo );<br />
ptr = Marshal.AllocHGlobal( size );<br />
<br />
Marshal.StructureToPtr(<br />
m_driveInfo,<br />
ptr,<br />
false<br />
);<br />
<br />
<br />
int result = 0;<br />
if ( ( result = GetTapeParameters(<br />
m_handleValue,<br />
DRIVE_PARAMS,<br />
ref size,<br />
ptr ) ) != NO_ERROR )<br />
{<br />
throw new TapeOperatorWin32Exception(<br />
"GetTapeParameters", Marshal.GetLastWin32Error() ); <br />
}<br />
<br />
m_driveInfo = ( DriveInfo )<br />
Marshal.PtrToStructure( ptr, typeof( DriveInfo ) );<br />
}<br />
<br />
<br />
return m_driveInfo.DefaultBlockSize;<br />
}<br />
finally<br />
{<br />
if ( ptr != IntPtr.Zero )<br />
{<br />
Marshal.FreeHGlobal( ptr );<br />
}<br />
}<br />
}<br />
}<br />
#endregion<br />
<br />
#region Private methods<br />
<br />
private uint GetBlocksNumber(long bytes)<br />
{<br />
uint numberOfBlocks = ( uint )bytes / BlockSize;<br />
uint bytesInLastBlock = ( uint )bytes % BlockSize;<br />
<br />
if ( bytesInLastBlock > 0 ) numberOfBlocks++;<br />
<br />
return numberOfBlocks;<br />
}<br />
#endregion<br />
}<br />
<br />
public class TapeOperatorWin32Exception : ApplicationException<br />
{<br />
public TapeOperatorWin32Exception( string methodName, int win32ErroCode ):<br />
base( string.Format(<br />
"WIN32 API method failed : {0} failed with error code {1}",<br />
methodName,<br />
win32ErroCode<br />
) ){}<br />
}<br />
<br />
}
-- modified at 6:56 Tuesday 12th September, 2006
|
|
|
|
|
Hi,
I think that something wrong with your tape media.
Check that it is formatted and blocksize is set
(GetTapeParameter API for media).
I'll send the code for this test ASAP.
Good luck!
Dima
|
|
|
|
|
Check that partitions and blocksize are defined in your media:
[StructLayout(LayoutKind.Sequential)]
public struct MediaInfo
{
public long Capacity;
public long Remaining;
public uint BlockSize;
public uint PartitionCount;
public byte IsWriteProtected;
}
public MediaInfo GetTapeMediaParameters()
{
IntPtr ptr = IntPtr.Zero;
try
{
MediaInfo mediaInfo = new MediaInfo();
// Allocate unmanaged memory
int size = Marshal.SizeOf(mediaInfo);
ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(
mediaInfo,
ptr,
false
);
int result = 0;
if ((result = GetTapeParameters(
m_handleValue,
MEDIA_PARAMS,
ref size,
ptr)) != NO_ERROR)
{
throw new TapeOperatorWin32Exception(
"GetTapeParameters",
Marshal.GetLastWin32Error());
}
// Get managed media Info
mediaInfo = (MediaInfo)
Marshal.PtrToStructure(ptr, typeof(MediaInfo));
return mediaInfo;
}
finally
{
if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr);
}
}
If blocksize is zero, set appropriate blockSize by SetTapeParameters Win32 API
You can set partiotions number as well.
Dima
|
|
|
|
|
Thank you. But I tried to do that. Only I have a problem.
When I write to the tape, an exception is thrown at the Flush function saying that the file stream needs to be declared as SYNCHRONOUS.
But when I read from the tape, an exception is thrown at the Read function saying that the file stream needs to be declared as A-SYNCHRONOUS.
Note that I declared the file as synchronous but this does not prevent the write funvtion to throw the exception above!
What can I do?
|
|
|
|
|
Hi, check that:
1) OS really recognize your device ( Control panel -> devices )
2) tape is realy loaded into drive of your device
3) Load method ( especially LoadTape call ) was executed
without errors( Pay attention you can use Read/Write methods ONLY after
Load method )
If 1-3 is OK for you, and you still have the same problem,
send changed code of TapeOperator and I'll try to help you.
( dmitrys@elbit.co.il )
Best regards!;P
Dima
|
|
|
|
|
I'm experiencing the same problem.
Also will it support block sizes larger than 64k? Is there a limit?
thanks
|
|
|
|
|
It depends on media type and device.
Usually I am using 1MB+ as block size.
Dima S
|
|
|
|
|