#!/usr/bin/perl # This decodes the cartridge memory on LTO tapes # Robin H. Johnson # Usage: # sg_raw -o - -r 1024 -t 60 -v /dev/nst0 8c 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 | ./read_attribute.pl # OR # sg_read_attr -r {DEVICE} > /tmp/lto use strict; use warnings; use bignum; use bigint; sub commify { local $_ = shift; undef while s/^(-?\d+)(\d{3})/$1.$2/; return $_; } sub decode_id { my $id = shift; my $s; $s = 'Reserved' if ( ($id >> 8) >= 0x18) ; $s = 'Host (vendor-specific)' if ( ($id >> 8) < 0x18) ; $s = 'Medium (vendor-specific)' if ( ($id >> 8) < 0x14) ; $s = 'Device (vendor-specific)' if ( ($id >> 8) < 0x10) ; $s = 'Medium' if ( ($id >> 8) < 0x08) ; $s = 'Host' if ( ($id >> 8) < 0x0C) ; $s = 'Device' if ( ($id >> 8) < 0x04) ; my %ATTRIBUTES = ( # Media Auxiliary Memory attribute informaton gleaned from various sources including: # # "IBM LTO Ultrium tape drive" - "scsi reference" (8th edition) # "HP LTO Ultrium 6 tape drives technical reference manual" "Volume 3 - Host Interface Guide" # "Quantum LTO 3 User Manual" # Discussion with MP Tapes # # These are probably more fully documented at t10.org however this requires membership fees # and may entail distribution restrictions. # 0x0000 => "Remaining Native Capacity in Partition (MiB)", # 8 bytes, Binary # 0x0001 => "Maximum Native Capacity in Partition (MiB)", # 8 bytes, Binary # 0x0002 => "TapeAlert Flags", # 8 bytes, Binary # The tape drive/autoloader flag definition is grouped into the following sections: # Flags 1 to 19: For tape drive write/read management # Flags 20 to 25: For cleaning management # Flags 26 to 39: For tape drive hardware errors # Flags 40 to 49:For tape autoloader errors # # 1 bit per flag (MSB=flag1, LSB=flag64) # Bits specify flags set during last load. # Minimum allowable set: # 3 - Hard error # 4 - Media # 5 - Read Failure # 6 - Write failure # 20 - Clean now # 22 - Cleaning cycle failed (expired cleaning) # 31 - Hardware B (POST failed) # See http://www.t10.org/ftp/t10/document.02/02-142r0.pdf and # http://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1 # 0x0003 => "Lifetime Load Count", # 8 bytes, Binary # 0x0004 => "MAM Space Remaining (bytes)", # 8 bytes, Binary # 0x0005 => "Assigning Organization", # 8 bytes, ASCII "LTO-CVE" # 0x0006 => "Formatted Density Code", # 1 bytes, Binary # 0x40 - LTO-1 # 0x42 - LTO-2 # 0x44 - LTO-3 # 0x46 - LTO-4 # 0x58 - LTO-5 # 0x5A - LTO-6 # 0x5C - LTO-7 # 0x5D - LTO-M8 # 0x5E - LTO-8 # (this information used to be maintained by T10.org but no longer is.) # (Future density codes will need need to be empirically determined ) # 0x0007 => "Lifetime Initialization Count (media formatted)", # 1 bytes, Binary # 0 = not initialised # 1 = all other cases # 0x0008 => "Volume Identifier", # 0-32 bytes, ASCII # 0x0009 => "Volume Change Reference", # 4 bytes, Binary # Number of changes to objects on the volume (written objects, overwrites, formatted) # 0x020A => "Tape Device Make/Serial Number at Last Load", # 40 bytes, ASCII # Byte 0 - 7 - T10 vendor ID # Byte 8 - 39 - Device serial number # 0x020B => "Tape Device Make/Serial Number at Load - 1", # 40 bytes, ASCII # Byte 0 - 7 - T10 vendor ID # Byte 8 - 39 - Device serial number # 0x020C => "Tape Device Make/Serial Number at Load - 2", # 40 bytes, ASCII # Byte 0 - 7 - T10 vendor ID # Byte 8 - 39 - Device serial number # 0x020D => "Tape Device Make/Serial Number at Load - 3", # 40 bytes, ASCII # Byte 0 - 7 - T10 vendor ID # Byte 8 - 39 - Device serial number # 0x0220 => "Total Megabytes Written in Medium Life", # 8 bytes, Binary # 0x0221 => "Total Megabytes Read in Medium Life", # 8 bytes, Binary # 0x0222 => "Total Megabytes Written in Current/Last Load", # 8 bytes, Binary # 0x0223 => "Total Megabytes Read in Current/Last Load", # 8 bytes, Binary # 0x0224 => "Logical position of first encrypted block", # 8 bytes, Binary # 0xffff ffff ffff ffff - unencrypted # 0xffff ffff ffff fffe - unknown # Everything else is logical address of first encrypted block # 0x0225 => "Logical position of first unencrypted block after the first encrypted block", # 8 bytes, Binary # 0xffff ffff ffff ffff - unencrypted if 0x0224 = 0xffff ffff ffff fffe # 0xffff ffff ffff ffff - no unencrypted blocks if 0x0224 != (0xffff ffff ffff ffff or 0xffff ffff ffff fffe) # 0xffff ffff ffff fffe - unknown if any unencrypted blocks. # Everything else is logical address of first unencrypted block # 0x0340 => "Medium Usage History", # 90 bytes, Binary # Byte: 0 - 5 - This load - amount of data written (MiB) # 6 - 11 - This load - write retries count # 12 - 17 - This load - amount of data read (MiB) # 18 - 23 - This load - read retries count # 24 - 29 - Last load - amount of data written (MiB) # 30 - 35 - Last load - write retries count # 36 - 41 - Last load - amount of data read (MiB) # 42 - 47 - Last load - read retries count # 48 - 53 - Since last format - amount of data written (MiB) # 54 - 59 - Since last format - write retries count # 60 - 65 - Since last format - amount of data read (MiB) # 66 - 71 - Since last format - read retries count # 72 - 77 - Since last format - load count. # 78 - 83 - Total change partition count (0) # 84 - 89 - Total partition initialise count (0) # 0x0341 => "Partition Usage History", # 60 bytes, Binary # Provides counters for the partition specified by the PARTITION NUMBER field in the CDB. # Data is presented in the same format as the MEDIUM USAGE HISTORY (ID 0340h). # Note that since there is only one partition on the LTO media the PARTITION USAGE HISTORY # will be identical to the MEDIUM USAGE HISTORY # 0x0400 => "Medium Manufacturer", # 8 bytes, ASCII (T10 vendor ID) # 0x0401 => "Medium Serial Number", # 32 bytes, ASCII # 0x0402 => "Medium Length (metres)", # 4 bytes, Binary # 0x0403 => "Medium Width (* 0.1 mm)", # 4 bytes, Binary # 127 (width of tape in 0.1mm units) # 0x0404 => "Assigning Organization", # 8 bytes, ASCII "LTO-CVE" # 0x0405 => "Medium Density Code", # 1 bytes, Binary # Same values as 0x006 Formatted Density Code. # 0x0406 => "Medium Manufacture Date (yyyymmdd)", # 8 bytes, ASCII # 0x0407 => "MAM total capacity (bytes)", # 8 bytes, Binary # 0x0408 => "Medium Type", # 1 bytes, Binary # 0x00 = Data cartridge # 0x01 = Cleaning cartridge # 0x80 = WORM cartridge # 0x0409 => "Medium Type Information (for cleaning media only, max cycles permitted)", # 2 bytes, Binary # For cleaning media only - Maximum number of cycles permitted. # 0x040A => "Numeric Medium Serial Number", # 0x0800 => "Application Vendor", # 8 bytes, ASCII (T10 vendor ID) # 0x0801 => "Application Name", # 32 bytes, ASCII # 0x0802 => "Application Version", # 8 bytes, ASCII # 0x0803 => "User Medium Text Label", # 160 bytes, Text # 0x0804 => "Date & Time Last Written (yyyymmddhhmm)", # 12 bytes, ASCII # 0x0805 => "Text Localization Identifier", # 1 bytes, Binary # 0x00 = No code (ASCII) # 0x01 = ISO/IEC8859-1 (Europe,Latin America) # 0x02 = ISO/IEC8859-2 (Eastern Europe) # 0x03 = ISO/IEC8859-3 (SE Europe/miscellaneous) # 0x04 = ISO/IEC8859-4 (Scandinavia/Baltic) # 0x05 = ISO/IEC8859-5 (Cyrillic) # 0x06 = ISO/IEC8859-6 (Arabic) # 0x07 = ISO/IEC8859-7 (Greek) # 0x08 = ISO/IEC8859-8 (Hebrew) # 0x09 = ISO/IEC8859-9 (Latin5) # 0x0a = ISO/IEC8859-10(Latin6) # 0x80 = ISO/IEC10646 (Unicode) # 0x81 = ISO/IEC10646 -1, Amendment no. 2 (UTF-8) # 0x0806 => "Barcode", # 32 bytes, ASCII # 0x0807 => "Owning Host Textual Name", # 80 bytes, Text # 0x0808 => "Media Pool", # 160 bytes, Text # 0x0809 => "Partition User Text Label", # 16 bytes, ASCII # 0x080A => "Load/Unload at Partition", # 1 bytes, Binary # 0 = Load/unload at start of tape (default) # 1 = Load/unload at partition specified in CDB # 0x080B => "Application Format Version", # 16 bytes, ASCII # 0x080C => "Volume Coherency Information", # 23-n, Binary # Bytes 0 - Attribute length (n) # 1 - n - Volume change reference value # n+1 - n+8 - Volume coherency count # n+9 - n+16 - Volume coherency set identifier # n+17 - n+18 - Application client specific information length (y-(n+18)) # n+19 - y - Application client specific information # 0x0820 => "Medium Globally Unique Identifer (LTFS)", # 36 bytes, Binary # 0x0821 => "Media Pool Globally Unique Identifer (LTFS)", # 36 bytes, Binary # 0x0e0a => "MP Tapes - eraser info", # 54 bytes, Text (Encrypted) # Proprietary data written to LTO-MAM by the MP Tapes cleaning or erasure machines # This contains device details (model/serial number) and date. # 0x1000 => "Unique Cartridge Identity (UCI)", # 28 bytes, Binary # Bytes 0 - 3 - Binary - LTO CM serial (4 bytes) # Bytes 4 - 11 - Binary - Tape pancake ID from manufacturer (8 bytes) # Bytes 12 - 19 - ASCII - Manufacturer name (8 bytes) # Bytes 20 - 23 - Binary - LPOS value at LP1 (4 bytes) # Bytes 24 - 25 - Binary - Cartridge Type (2 bytes) # Bytes 26 - 27 - Reserved - Zero - 2 bytes # 0x1001 => "Alternate Unique Cartridge Identity (Alt-UCI)", # 24 bytes, Binary # Bytes 0 - 3 - Binary - LTO CM serial (4 bytes) # Bytes 4 - 11 - Binary - Tape pancake ID from manufacturer (8 bytes) # Bytes 12 - 21 - Binary - Cartridge serial # (10 bytes) # Bytes 22 - 23 - Binary - Cartridge Type (2 bytes) # 0x1800 => "Automation Vendor", # 8 bytes, ASCII, T10 vendor ID # 0x1800 => "Automation Product ID", # 16 bytes, ASCII # 0x1800 => "Automation Product revision level", # 4 bytes, ASCII # 0x1800 => "Barcode", # 32 bytes, ASCII # ); $s .= ': '; $s .= (defined $ATTRIBUTES{$id}) ? $ATTRIBUTES{$id} : 'UNKNOWN'; return $s; } sub decode_read_attribute { my $buf = shift; my $offset = 0; #my ($size) = unpack('N', $buf); #printf("size=%d 0x%x\n", $size, $size); $offset += 4; while($offset < length($buf)) { my ($id, $tmp, $attrlen) = unpack('nCn', substr($buf, $offset, 5)); my $ro = $tmp >> 7; my $format = $tmp & 0x03; $offset += 5; my $val = substr($buf, $offset, $attrlen); printf( "id=0x%04x writable=%s format=%x attrlen=0x%04x=%-5d - %s\n", $id, $ro ? "RO" : "RW", $format, $attrlen, $attrlen, decode_id($id)); if($id == 0x1000 ) { print "\t\t\t\t\t\t\t - LTO-CM serial #: ", hex(unpack('H*', substr($val, 0, 4))), "\n", "\t\t\t\t\t\t\t - Manf Tape pancake Id: ", unpack('A*', substr($val, 4, 8)), "\n", "\t\t\t\t\t\t\t - Manufacturer: ", unpack('A*', substr($val, 12, 8)), "\n", "\t\t\t\t\t\t\t - LPOS at LP1: ", hex(unpack('H*', substr($val, 20, 4))), "\n", "\t\t\t\t\t\t\t - Cartridge type ", hex(unpack('H*', substr($val, 24, 2))), "\n"; } elsif($id == 0x1001 ) { print "\t\t\t\t\t\t\t - LTO-CM serial #: ", hex(unpack('H*', substr($val, 0, 4))), "\n ", "\t\t\t\t\t\t\t - Manf Tape pancake Id: ", unpack('A*', substr($val, 4, 8)), "\n ", "\t\t\t\t\t\t\t - Cartridge Serial #: ", unpack('A*', substr($val, 12, 10)), "\n", "\t\t\t\t\t\t\t - Cartridge type ", hex(unpack('H*', substr($val, 21, 2))), "\n"; } elsif($id == 0x0e0a ) { print "\t\t\t\t\t\t\t\t - mptapes.com - Encrypted data \n"; } elsif($id == 0x0409 ) { print "\t\t\t\t\t\t\t val=0x",unpack('H*', $val)," (", commify(hex(unpack('H*', $val))),") \n"; if (unpack('H*', $val) =~ "00") { print "\t\t\t\t\t\t\t - (Only applicable to cleaning tapes) \n"; } else { print "\t\t\t\t\t\t\t\t - (Maximum number of cleaning cycles with this cleaning tape) \n"; } } elsif($id == 0x0408 ) { print "\t\t\t\t\t\t\t val=0x",unpack('H*', $val), ", "; if (unpack('H*', $val) =~ "00") { print "(DATA cartridge) \n"; } elsif (unpack('H*', $val) =~ "01") { print "(CLEANING cartridge) \n"; } elsif (unpack('H*', $val) =~ "80") { print "(WORM cartridge) \n"; } else { print "UNKNOWN - PLEASE UPDATE ME \n"; } } elsif($id == 0x0341 ) { print "\t\t\t\t\t\t\t\t - This load - MiB written: ", commify(hex(unpack('H*', substr($val, 0, 6)))), "\n", "\t\t\t\t\t\t\t\t - This load - write retries: ", commify(hex(unpack('H*', substr($val, 6, 6)))), " \n", "\t\t\t\t\t\t\t\t - This load - MiB read: ", commify(hex(unpack('H*', substr($val, 12, 6)))), " \n", "\t\t\t\t\t\t\t\t - This load - read retries: ", commify(hex(unpack('H*', substr($val, 18, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - MiB written: ", commify(hex(unpack('H*', substr($val, 24, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - write retries: ", commify(hex(unpack('H*', substr($val, 30, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - MiB read: ", commify(hex(unpack('H*', substr($val, 36, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - read retries: ", commify(hex(unpack('H*', substr($val, 42, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - MiB written: ", commify(hex(unpack('H*', substr($val, 48, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - write retries: ", commify(hex(unpack('H*', substr($val, 54, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - MiB read: ", commify(hex(unpack('H*', substr($val, 60, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - read retries: ", commify(hex(unpack('H*', substr($val, 66, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - load count: ", commify(hex(unpack('H*', substr($val, 72, 6)))), " \n", "\t\t\t\t\t\t\t\t - Total change partition count: ", commify(hex(unpack('H*', substr($val, 78, 6)))), " \n", "\t\t\t\t\t\t\t\t - Total partition ininialise count:", commify(hex(unpack('H*', substr($val, 84, 6)))), " \n"; } elsif($id == 0x0340 ) { print "\t\t\t\t\t\t\t\t - This load - MiB written: ", commify(hex(unpack('H*', substr($val, 0, 6)))), "\n", "\t\t\t\t\t\t\t\t - This load - write retries: ", commify(hex(unpack('H*', substr($val, 6, 6)))), " \n", "\t\t\t\t\t\t\t\t - This load - MiB read: ", commify(hex(unpack('H*', substr($val, 12, 6)))), " \n", "\t\t\t\t\t\t\t\t - This load - read retries: ", commify(hex(unpack('H*', substr($val, 18, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - MiB written: ", commify(hex(unpack('H*', substr($val, 24, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - write retries: ", commify(hex(unpack('H*', substr($val, 30, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - MiB read: ", commify(hex(unpack('H*', substr($val, 36, 6)))), " \n", "\t\t\t\t\t\t\t\t - Last load - read retries: ", commify(hex(unpack('H*', substr($val, 42, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - MiB written: ", commify(hex(unpack('H*', substr($val, 48, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - write retries: ", commify(hex(unpack('H*', substr($val, 54, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - MiB read: ", commify(hex(unpack('H*', substr($val, 60, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - read retries: ", commify(hex(unpack('H*', substr($val, 66, 6)))), " \n", "\t\t\t\t\t\t\t\t - Since format - load count: ", commify(hex(unpack('H*', substr($val, 72, 6)))), " \n", "\t\t\t\t\t\t\t\t - Total change partition count: ", commify(hex(unpack('H*', substr($val, 78, 6)))), " \n", "\t\t\t\t\t\t\t\t - Total partition ininialise count:", commify(hex(unpack('H*', substr($val, 84, 6)))), " \n"; } elsif($id == 0x0225 ) { print "\t\t\t\t\t\t\t val=0x", unpack('H*', $val), " "; if (unpack('H*', $val) =~ "ffffffffffffffff") { print " (Not applicable or no unencrypted blocks) \n"; } elsif (unpack('H*', $val) =~ "fffffffffffffffe") { print " (Start unencrypted block unknown) \n"; } else { print "\n"; } } elsif($id == 0x0224 ) { print "\t\t\t\t\t\t\t val=0x", unpack('H*', $val), " "; if (unpack('H*', $val) =~ "ffffffffffffffff") { print " (NOT ENCRYPTED) \n"; } elsif (unpack('H*', $val) =~ "fffffffffffffffe") { print " (Starting encypted block unknown) \n"; } else { print "\n"; } } elsif($id == 0x0006 ) { print "\t\t\t\t\t\t\t val=0x",unpack('H*', $val), ", "; if (unpack('H*', $val) =~ "40") { print "(LTO-1) \n"; } elsif (unpack('H*', $val) =~ "42") { print "(LTO-2) \n"; } elsif (unpack('H*', $val) =~ "44") { print "(LTO-3) \n"; } elsif (unpack('H*', $val) =~ "46") { print "(LTO-4) \n"; } elsif (unpack('H*', $val) =~ "58") { print "(LTO-5) \n"; } elsif (unpack('H*', $val) =~ "5a") { print "(LTO-6) \n"; } elsif (unpack('H*', $val) =~ "5c") { print "(LTO-7) \n"; } elsif (unpack('H*', $val) =~ "5d") { print "(LTO-M8) \n"; } elsif (unpack('H*', $val) =~ "5e") { print "(LTO-8) \n"; } else { print "UNKNOWN - PLEASE UPDATE ME \n"; } } elsif($id == 0x0002 ) { print "\t\t\t\t\t\t\t val=0x",unpack('H*', $val)," (",unpack('b*', $val),") \n"; } elsif($format == 0) { print "\t\t\t\t\t\t\t val=0x",unpack('H*', $val)," (",commify(hex(unpack('H*', $val))),") \n"; } elsif($format == 1 || $format == 2) { print "\t\t\t\t\t\t\t - ",unpack('A*', $val), "\n"; } $offset += $attrlen; } } my $buf; read STDIN, $buf, 2**16; if ( length($buf) > 10 ) { decode_read_attribute($buf); }