尝试在 python 中使用 ioctl 读取驱动器属性时出现溢出错误

问题描述 投票:0回答:3

当尝试为我的一个驱动器提取特定于供应商的功能时 (cmd = 0x89),我收到溢出错误:

OverflowError: signed integer is greater than maximum
。调用 ioctl 后立即抛出该错误。回溯也不是很有信息:

Traceback (most recent call last):
  File "lockbx/atacmd.py", line 148, in <module>
    GetDriveIdSgIo("/dev/sda")
  File "lockbx/atacmd.py", line 134, in GetDriveIdSgIo
    ret = fcntl.ioctl(fd.fileno(), SG_IO, ctypes.addressof(sgio))
OverflowError: signed integer is greater than maximum

ATA 命令传递参考:http://www.t10.org/ftp/t10/document.04/04-262r8.pdf

class AtaCmd(ctypes.Structure):

    _fields_ = [
        ('opcode', ctypes.c_ubyte),
        ('protocol', ctypes.c_ubyte),
        ('flags', ctypes.c_ubyte),
        ('features', ctypes.c_ubyte),
        ('sector_count', ctypes.c_ubyte),
        ('lba_low', ctypes.c_ubyte),
        ('lba_mid', ctypes.c_ubyte),
        ('lba_high', ctypes.c_ubyte),
        ('device', ctypes.c_ubyte),
        ('command', ctypes.c_ubyte),
        ('reserved', ctypes.c_ubyte),
        ('control', ctypes.c_ubyte)]


class SgioHdr(ctypes.Structure):
    """<scsi/sg.h> sg_io_hdr_t."""

    _fields_ = [
        ('interface_id', ctypes.c_int),
        ('dxfer_direction', ctypes.c_int),
        ('cmd_len', ctypes.c_ubyte),
        ('mx_sb_len', ctypes.c_ubyte),
        ('iovec_count', ctypes.c_ushort),
        ('dxfer_len', ctypes.c_uint),
        ('dxferp', ctypes.c_void_p),
        ('cmdp', ctypes.c_void_p),
        ('sbp', ctypes.c_void_p),
        ('timeout', ctypes.c_uint),
        ('flags', ctypes.c_uint),
        ('pack_id', ctypes.c_int),
        ('usr_ptr', ctypes.c_void_p),
        ('status', ctypes.c_ubyte),
        ('masked_status', ctypes.c_ubyte),
        ('msg_status', ctypes.c_ubyte),
        ('sb_len_wr', ctypes.c_ubyte),
        ('host_status', ctypes.c_ushort),
        ('driver_status', ctypes.c_ushort),
        ('resid', ctypes.c_int),
        ('duration', ctypes.c_uint),
        ('info', ctypes.c_uint)]


def GetDriveIdSgIo(dev):
    ata_cmd = AtaCmd(opcode=0xA1,  # ATA PASS-THROUGH (12)
                     protocol=3 << 1,  # PIO Data-In
                     flags=0x06,
                     features=0xF5,
                     sector_count=1,
                     lba_low=0, lba_mid=0, lba_high=0,
                     device=0,
                     command=0x89,  # IDENTIFY
                     reserved=0,
                     control=0)

    SG_DXFER_FROM_DEV = -3
    sense = ctypes.c_buffer(64)
    identify = ctypes.c_buffer(512)

    sgio = SgioHdr(
        interface_id=ord('S'),
        dxfer_direction=SG_DXFER_FROM_DEV,
        cmd_len=ctypes.sizeof(ata_cmd),
        mx_sb_len=ctypes.sizeof(sense),
        iovec_count=0,
        dxfer_len=ctypes.sizeof(identify),
        dxferp=0,
        cmdp=ctypes.addressof(ata_cmd),
        sbp=ctypes.addressof(sense),
        timeout=3000,
        flags=0,
        pack_id=0,
        usr_ptr=None,
        # Set values
        status=0, masked_status=0, msg_status=0, sb_len_wr=0, host_status=0,
        driver_status=0, resid=0, duration=0, info=0
    )

    SG_IO = 0x2285  # <scsi/sg.h>
    with open(dev, 'r') as fd:
        ret = fcntl.ioctl(fd.fileno(), SG_IO, ctypes.addressof(sgio))  # fails here

    # some other stuff returns endian swapped buffer info but not relevant

GetDriveIdSgIo("/dev/sda")

我哪里出错了?

python linux ioctl scsi ata
3个回答
1
投票

试试这个:

ret = fcntl.ioctl(fd.fileno(), 0x2285, ctypes.addressof(sgio))

也许 python 将 SG_IO 存储为 int64,但 ioctl 只接受 32 位整数作为 arg。或者明确地将其转换为 32 位:

c_uint(0x2285)

如果这不起作用,可能是 arg 参数的问题。试试这个:

ret = fcntl.ioctl(fd.fileno(), 0x2285, sgio)

如果这不起作用,请尝试使用

struct.pack
而不是
ctypes
作为 sgio 参数,并使用
struct.unpack
来读取 ret。

参考:https://docs.python.org/2/library/struct.html


1
投票

您收到的是 32 位还是 64 位? 如果我没记错的话,默认情况下它是 ctypes 中的 32 位值。


0
投票

晚了 3 年,但我自己也遇到过这个问题,解决方案就是使用 libc ioctl。

# We use libc instead of the builtin ioctl as the builtin can have
# issues with 64-bit pointers.
result = get_libc().ioctl(
    self.fd, IOCTL_SG_IO, ctypes.byref(sg_io_header)
)

get_libc() 的位置是:

import ctypes
from functools import cache


@cache
def get_libc():
    # Opens the libc.so, which can be quite a slow process, and
    # saves it for future use.
    return ctypes.CDLL("libc.so.6", use_errno=True)

这就是我的 SMARTie 库中所做的事情。

© www.soinside.com 2019 - 2024. All rights reserved.