1. #!/usr/bin/python
  2. import sys
  3. import fcntl
  4. import struct
  5. import ctypes
  6. import hashlib
  7. import hmac
  8.  
  9. class AtaCmd(ctypes.Structure):
  10.   """ATA Command Pass-Through
  11.     http://www.t10.org/ftp/t10/document.04/04-262r8.pdf"""
  12.  
  13.   _fields_ = [
  14.       ('opcode', ctypes.c_ubyte),
  15.       ('protocol', ctypes.c_ubyte),
  16.       ('flags', ctypes.c_ubyte),
  17.       ('features', ctypes.c_ubyte),
  18.       ('sector_count', ctypes.c_ubyte),
  19.       ('lba_low', ctypes.c_ubyte),
  20.       ('lba_mid', ctypes.c_ubyte),
  21.       ('lba_high', ctypes.c_ubyte),
  22.       ('device', ctypes.c_ubyte),
  23.       ('command', ctypes.c_ubyte),
  24.       ('reserved', ctypes.c_ubyte),
  25.       ('control', ctypes.c_ubyte) ]
  26.  
  27.  
  28. class SgioHdr(ctypes.Structure):
  29.   """<scsi/sg.h> sg_io_hdr_t."""
  30.  
  31.   _fields_ = [
  32.       ('interface_id', ctypes.c_int),
  33.       ('dxfer_direction', ctypes.c_int),
  34.       ('cmd_len', ctypes.c_ubyte),
  35.       ('mx_sb_len', ctypes.c_ubyte),
  36.       ('iovec_count', ctypes.c_ushort),
  37.       ('dxfer_len', ctypes.c_uint),
  38.       ('dxferp', ctypes.c_void_p),
  39.       ('cmdp', ctypes.c_void_p),
  40.       ('sbp', ctypes.c_void_p),
  41.       ('timeout', ctypes.c_uint),
  42.       ('flags', ctypes.c_uint),
  43.       ('pack_id', ctypes.c_int),
  44.       ('usr_ptr', ctypes.c_void_p),
  45.       ('status', ctypes.c_ubyte),
  46.       ('masked_status', ctypes.c_ubyte),
  47.       ('msg_status', ctypes.c_ubyte),
  48.       ('sb_len_wr', ctypes.c_ubyte),
  49.       ('host_status', ctypes.c_ushort),
  50.       ('driver_status', ctypes.c_ushort),
  51.       ('resid', ctypes.c_int),
  52.       ('duration', ctypes.c_uint),
  53.       ('info', ctypes.c_uint)]
  54.  
  55. def SwapString(str):
  56.   """Swap 16 bit words within a string.
  57.  
  58.  String data from an ATA IDENTIFY appears byteswapped, even on little-endian
  59.  achitectures. I don't know why. Other disk utilities I've looked at also
  60.  byte-swap strings, and contain comments that this needs to be done on all
  61.  platforms not just big-endian ones. So... yeah.
  62.  """
  63.   s = []
  64.   for x in range(0, len(str) - 1, 2):
  65.     s.append(str[x+1])
  66.     s.append(str[x])
  67.   return ''.join(s).strip()
  68.  
  69. def GetDriveIdSgIo(dev):
  70.   """Return information from interrogating the drive.
  71.  
  72.  This routine issues a SG_IO ioctl to a block device, which
  73.  requires either root privileges or the CAP_SYS_RAWIO capability.
  74.  
  75.  Args:
  76.    dev: name of the device, such as 'sda' or '/dev/sda'
  77.  
  78.  Returns:
  79.    (serial_number, fw_version, model) as strings
  80.  """
  81.  
  82.   if dev[0] != '/':
  83.     dev = '/dev/' + dev
  84.   ata_cmd = AtaCmd(opcode=0xa1,  # ATA PASS-THROUGH (12)
  85.                    protocol=4<<1,  # PIO Data-In
  86.                    # flags field
  87.                    # OFF_LINE = 0 (0 seconds offline)
  88.                    # CK_COND = 1 (copy sense data in response)
  89.                    # T_DIR = 1 (transfer from the ATA device)
  90.                    # BYT_BLOK = 1 (length is in blocks, not bytes)
  91.                    # T_LENGTH = 2 (transfer length in the SECTOR_COUNT field)
  92.                    flags=0x2e,
  93.                    features=0, sector_count=0,
  94.                    lba_low=0, lba_mid=0, lba_high=0,
  95.                    device=0,
  96.                    command=0xec,  # IDENTIFY
  97.                    reserved=0, control=0)
  98.   ASCII_S = 83
  99.   SG_DXFER_FROM_DEV = -3
  100.   sense = ctypes.c_buffer(64)
  101.   identify = ctypes.c_buffer(512)
  102.   sgio = SgioHdr(interface_id=ASCII_S, dxfer_direction=SG_DXFER_FROM_DEV,
  103.                  cmd_len=ctypes.sizeof(ata_cmd),
  104.                  mx_sb_len=ctypes.sizeof(sense), iovec_count=0,
  105.                  dxfer_len=ctypes.sizeof(identify),
  106.                  dxferp=ctypes.cast(identify, ctypes.c_void_p),
  107.                  cmdp=ctypes.addressof(ata_cmd),
  108.                  sbp=ctypes.cast(sense, ctypes.c_void_p), timeout=3000,
  109.                  flags=0, pack_id=0, usr_ptr=None, status=0, masked_status=0,
  110.                  msg_status=0, sb_len_wr=0, host_status=0, driver_status=0,
  111.                  resid=0, duration=0, info=0)
  112.   SG_IO = 0x2285  # <scsi/sg.h>
  113.   with open(dev, 'r') as fd:
  114.     if fcntl.ioctl(fd, SG_IO, ctypes.addressof(sgio)) != 0:
  115.       print "fcntl failed"
  116.       return None
  117.     if ord(sense[0]) != 0x72 or ord(sense[8]) != 0x9 or ord(sense[9]) != 0xc:
  118.       return None
  119.     # IDENTIFY format as defined on pg 91 of
  120.     # http://t13.org/Documents/UploadedDocuments/docs2006/D1699r3f-ATA8-ACS.pdf
  121.     serial_no = SwapString(identify[20:40])
  122.     fw_rev = SwapString(identify[46:53])
  123.     model = SwapString(identify[54:93])
  124.     return (serial_no, fw_rev, model)
  125.  
  126. def GetDriveId(dev):
  127.   """Return information from interrogating the drive.
  128.  
  129.  This routine issues a HDIO_GET_IDENTITY ioctl to a block device,
  130.  which only root can do.
  131.  
  132.  Args:
  133.    dev: name of the device, such as 'sda' or '/dev/sda'
  134.  
  135.  Returns:
  136.    (serial_number, fw_version, model) as strings
  137.  """
  138.   # from /usr/include/linux/hdreg.h, struct hd_driveid
  139.   # 10H = misc stuff, mostly deprecated
  140.   # 20s = serial_no
  141.   # 3H  = misc stuff
  142.   # 8s  = fw_rev
  143.   # 40s = model
  144.   # ... plus a bunch more stuff we don't care about.
  145.   struct_hd_driveid = '@ 10H 20s 3H 8s 40s'
  146.   HDIO_GET_IDENTITY = 0x030d
  147.   if dev[0] != '/':
  148.     dev = '/dev/' + dev
  149.   with open(dev, 'r') as fd:
  150.     buf = fcntl.ioctl(fd, HDIO_GET_IDENTITY, ' ' * 512)
  151.     fields = struct.unpack_from(struct_hd_driveid, buf)
  152.     serial_no = fields[10].strip()
  153.     fw_rev = fields[14].strip()
  154.     model = fields[15].strip()
  155.     return (serial_no, fw_rev, model)
  156.  
  157. if (len(sys.argv) > 1):
  158.         print GetDriveId(sys.argv[1])
  159.     (sn,fw,mod) = GetDriveIdSgIo(sys.argv[1])
  160.     key = hmac.new ('secret',mod.ljust(20,'\x00')+sn.ljust(40,'\x00'),hashlib.sha1)
  161.     print key.hexdigest()
  162. #model 20
  163. #serial 40
  164.  
  165. else:
  166.     print "Parameter missing"