Pythonからの直接ディスクアクセス

画像



今日は、Pythonからハードドライブインターフェイスへの移行を試みた方法と、その結果について説明します。



多数のハードドライブを定期的にテストする必要があります。 通常、ビクトリアのドスはネットにロードされます。 彼女はドライブを1つずつテストしますが、あまり便利ではありません。 さらに、IDEモードを備えていないボードは最近廃止され、タスクがさらに複雑になっています。 最初は、オープンソースを備えたLinux用の既製のソフトウェアを用意し、複数のディスクを並行してテストする機能を追加するというアイデアがありました。 すばやく検索した後、Linuxでこの領域の憂鬱な状態が明らかになりました。 セクターへのアクセス時間のテストとテスト中のエラーの種類に関する統計を行うソフトウェアから、私はwhddだけを見つけました。 whddコードを処理しようとして、完全に失敗しました。 プログラマーではない私にとって、コードは非常に混乱しているように見えました。 さらに、そのほとんどは鉄での作業ではありません。



単純な解決策は期待できないことに気付いたので、私は自分で同様のプログラムを書くことにしました。 Cで同様のプロジェクトをマスターできないことに気付いて、Pythonのディスクを直接操作する可能性について調査し始めました。Pythonを使用して、単純な問題を解決し、シンプルさとわかりやすさを愛しています。 猫はネットワーク上のこの問題に関する情報を求めて泣きましたが、それでもデバイスへのioctl要求の送信を許可するfcntlモジュールがあることがわかりました。 これで、コマンドをディスクに送信できます。 しかし、Linuxでは、すべてのディスクはscsiディスクと見なされます。テストのために、ataコマンドを直接ディスクに転送する必要があります。 scsiリクエストでataコマンドをラップできるATAコマンドパススルーメカニズムがあることが判明しました。 これの使用方法に関する基本情報は、sg3_utilsプロジェクトのソースから取得されました。 すべてをPythonで実装しようとしています。



C言語の構造に類似したPythonの構造を作成するために、ioctlへの後続の転送のために、ctypesモジュールがあります。 それとは別に、これらの構造を使用した奇妙なグリッチのデバッグから生じる白髪の量に言及する価値があります。 そこで、Cの構造の整列に関する知識を発見しました。その結果、2つの構造が生まれました。



ATAパススルーの構造:



class ataCmd(ctypes.Structure): _pack_ = 1 _fields_ = [ ('opcode', ctypes.c_ubyte), ('protocol', ctypes.c_ubyte), ('flags', ctypes.c_ubyte), ('features', ctypes.c_ushort), ('sector_count', ctypes.c_ushort), ('lba_h_low', ctypes.c_ubyte), ('lba_low', ctypes.c_ubyte), ('lba_h_mid', ctypes.c_ubyte), ('lba_mid', ctypes.c_ubyte), ('lba_h_high', ctypes.c_ubyte), ('lba_high', ctypes.c_ubyte), ('device', ctypes.c_ubyte), ('command', ctypes.c_ubyte), ('control', ctypes.c_ubyte)]
      
      





ioctlの構造:



 class sgioHdr(ctypes.Structure): _pack_ = 1 _fields_ = [ ('interface_id', ctypes.c_int), # [i] 'S' for SCSI generic (required) ('dxfer_direction', ctypes.c_int), # [i] data transfer direction ('cmd_len', ctypes.c_ubyte), # [i] SCSI command length ( <= 16 bytes) ('mx_sb_len', ctypes.c_ubyte), # [i] max length to write to sbp ('iovec_count', ctypes.c_ushort), # [i] 0 implies no scatter gather ('dxfer_len', ctypes.c_uint), # [i] byte count of data transfer ('dxferp', ctypes.c_void_p), # [i], [*io] points to data transfer memory ('cmdp', ctypes.c_void_p), # [i], [*i] points to command to perform ('sbp', ctypes.c_void_p), # [i], [*o] points to sense_buffer memory ('timeout', ctypes.c_uint), # [i] MAX_UINT->no timeout (unit: millisec) ('flags', ctypes.c_uint), # [i] 0 -> default, see SG_FLAG... ('pack_id', ctypes.c_int), # [i->o] unused internally (normally) ('usr_ptr', ctypes.c_void_p), # [i->o] unused internally ('status', ctypes.c_ubyte), # [o] scsi status ('masked_status', ctypes.c_ubyte), # [o] shifted, masked scsi status ('msg_status', ctypes.c_ubyte), # [o] messaging level data (optional) ('sb_len_wr', ctypes.c_ubyte), # [o] byte count actually written to sbp ('host_status', ctypes.c_ushort), # [o] errors from host adapter ('driver_status', ctypes.c_ushort), # [o] errors from software driver ('resid', ctypes.c_int), # [o] dxfer_len - actual_transferred ('duration', ctypes.c_uint), # [o] time taken by cmd (unit: millisec) ('info', ctypes.c_uint)] # [o] auxiliary information
      
      





これらの構造の充填は各ディスク操作の前に必要であり、多くのスペースを占有するため、この操作は別の機能で実行されます。 マルチバイト値では、バイト順を変更する必要があります。



 def prepareSgio(cmd, feature, count, lba, direction, sense, buf): if direction == SG_DXFER_FROM_DEV: buf_len = ctypes.sizeof(buf) buf_p = ctypes.cast(buf, ctypes.c_void_p) prot = 4 << 1 # PIO Data-In elif direction == SG_DXFER_TO_DEV: buf_len = ctypes.sizeof(buf) buf_p = ctypes.cast(buf, ctypes.c_void_p) prot = 5 << 1 # PIO Data-Out else: buf_len = 0 buf_p = None prot = 3 << 1 # Non-data if cmd != 0xb0: # not SMART COMMAND prot = prot | 1 # + EXTEND sector_lba = lba.to_bytes(6, byteorder='little') ata_cmd = ataCmd(opcode=0x85, # ATA PASS-THROUGH (16) protocol=prot, # flags field # OFF_LINE = 0 (0 seconds offline) # CK_COND = 1 (copy sense data in response) # T_DIR = 1 (transfer from the ATA device) # BYT_BLOK = 1 (length is in blocks, not bytes) # T_LENGTH = 2 (transfer length in the SECTOR_COUNT field) flags=0x2e, features=swap16(feature), sector_count=swap16(count), lba_h_low=sector_lba[3], lba_low=sector_lba[0], lba_h_mid=sector_lba[4], lba_mid=sector_lba[1], lba_h_high=sector_lba[5], lba_high=sector_lba[2], device=0, command=cmd, control=0) sgio = sgioHdr(interface_id=ASCII_S, dxfer_direction=direction, cmd_len=ctypes.sizeof(ata_cmd), mx_sb_len=ctypes.sizeof(sense), iovec_count=0, dxfer_len=buf_len, dxferp=buf_p, cmdp=ctypes.addressof(ata_cmd), sbp=ctypes.cast(sense, ctypes.c_void_p), timeout=1000, flags=0, pack_id=0, usr_ptr=None, status=0, masked_status=0, msg_status=0, sb_len_wr=0, host_status=0, driver_status=0, resid=0, duration=0, info=0) return sgio
      
      





この関数はataコマンド、パラメーター、バッファーを受け取り、ioctl要求の既製の構造を返します。 その後、すべてが簡単です。 コマンド実行のステータスとataステータスおよびエラーレジスタの内容が返されるバッファを作成します。 ディスクから読み取ったセクター用のバッファーを作成します。 構造を埋め、最初のataチームを実行します。



 sense = ctypes.c_buffer(64) identify = ctypes.c_buffer(512) sgio = prepareSgio(0xec, 0, 0, 0, SG_DXFER_FROM_DEV, sense, identify) # IDENTIFY with open(dev, 'r') as fd: if fcntl.ioctl(fd, SG_IO, ctypes.addressof(sgio)) != 0: return None # fcntl failed!
      
      





応答として、ディスク情報を含むセクターを取得します。



 0000000: 5a04 ff3f 37c8 1000 0000 0000 3f00 0000 Z..?7.......?... 0000010: 0000 0000 2020 2020 2020 4b4a 3131 3142 .... KJ111B 0000020: 3942 5647 4142 4659 0300 5fea 3800 4b4a 9BVGABFY.._.8.KJ 0000030: 4f41 3341 4145 6948 6174 6863 2069 5548 OA3AAEiHathc iUH 0000040: 3741 3232 3230 4130 414c 3333 2030 2020 7A2220A0AL33 0 0000050: 2020 2020 2020 2020 2020 2020 2020 1080 .. 0000060: 0040 002f 0040 0002 0002 0700 ff3f 1000 .@./.@.......?.. 0000070: 3f00 10fc fb00 0001 ffff ff0f 0000 0700 ?............... 0000080: 0300 7800 7800 7800 7800 0000 0000 0000 ..xxxx...... 0000090: 0000 0000 0000 1f00 0617 0000 5e00 4400 ............^.D. 00000a0: fc01 2900 6b34 697d 7347 6934 41bc 6347 ..).k4i}sGi4A.cG 00000b0: 7f40 0401 0000 0000 feff 0000 0000 0800 .@.............. 00000c0: ca00 f900 1027 0000 b088 e0e8 0000 0000 .....'.......... 00000d0: ca00 0000 0000 875a 0050 a2cc cb22 44fc .......ZP.."D. 00000e0: 0000 0000 0000 0000 0000 0000 0000 1440 ...............@ 00000f0: 1440 0000 0000 0000 0000 0000 0000 0000 .@.............. 0000100: 0100 0b00 0000 0000 8020 f10d 20fa 0100 ......... .. ... 0000110: 0040 0404 0403 0000 0000 0502 0604 0504 .@.............. 0000120: 0506 0803 0506 0504 0505 0603 0505 0000 ................ 0000130: 3741 3342 0000 0a78 0000 bd5d d3a1 0080 7A3B...x...].... 0000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0000150: 0200 0000 0000 0000 0000 0000 0000 0000 ................ 0000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0000190: 0000 0000 0000 0000 0000 0000 3d00 0000 ............=... 00001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00001b0: 0000 201c 0000 0000 0000 0000 1f10 2100 .. ...........!. 00001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00001d0: 0000 0000 0100 e003 0000 0000 0000 0000 ................ 00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00001f0: 0000 0000 0000 0000 0000 0000 0000 a503 ................
      
      





ディスクに関する完全な情報が含まれており、メインの情報を抽出します。



  serial = swapString(identify[20:40]) firmware = swapString(identify[46:53]) model = swapString(identify[54:93]) sectors = int.from_bytes(identify[200] + identify[201] + identify[202] + identify[203] + identify[204] + identify[205] + identify[206] + identify[207], byteorder='little')
      
      





その結果、以下が得られます。



モデル:Hitachi HUA722020ALA330; ファームウェア:JKAOA3; シリアル番号:JK11A1YAJE2N5V; セクター数:3907029168。



これで、ataコマンドをディスクに送信し、ディスクからの応答を受信できます。 ゆっくりと、私の仕事の結果は、SMARTの読み取りを含む基本的なataコマンドのセットの実装を含むライブラリで形になりました。 興味のある人は誰でもここで彼女を見ることができる。 コードの品質をscらないでください、私は魔術師のプログラマーではありません、ただ勉強しています。



現在は、テストユーティリティを作成するために使用する必要があります。 もっと多くの発見が私を待っていると感じます。



更新:

amaraoの推奨に従ってクラスと例外を使用してライブラリを書き直しました。 また、sgioという名前は図書館の目的に関して誤解を招くものであると判断しました。 ライブラリは現在、ataptと呼ばれ、 GitHubおよびpipで利用できます。 githubでの使用例があります。



All Articles