Question

In my Python (2.7.3) code, I'm trying to use an ioctl call, accepting a long int (64 bit) as an argument. I'm on a 64-bit system, so a 64-bit int is the same size as a pointer.

My problem is that Python doesn't seem to accept a 64-bit int as the argument for a fcntl.ioctl() call. It happily accepts a 32-bit int or a 64-bit pointer - but what I need is to pass a 64-bit int.

Here's my ioctl handler:

static long trivial_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    long err = 0;

    switch (cmd)
    {
        case 1234:
            printk("=== (%u) Driver got arg %lx; arg<<32 is %lx\n", cmd, arg, arg<<32);
            break;
        case 5678:
            printk("=== (%u) Driver got arg %lx\n", cmd, arg);
            break;
        default:
            printk("=== OH NOES!!! %u %lu\n", cmd, arg);
            err = -EINVAL;
    }

    return err;
}

In existing C code, I use the call like this:

static int trivial_ioctl_test(){
    int ret;
    int fd = open(DEV_NAME, O_RDWR);

    unsigned long arg = 0xffff;

    ret = ioctl(fd, 1234, arg); // ===(1234) Driver got arg ffff; arg<<32 is ffff00000000
    arg = arg<<32;
    ret = ioctl(fd, 5678, arg); // === (5678) Driver got arg ffff00000000
    close(fd);

}

In python, I open the device file, and then I get the following results:

>>> from fcntl import ioctl
>>> import os
>>> fd = os.open (DEV_NAME, os.O_RDWR, 0666)
>>> ioctl(fd, 1234, 0xffff)
0
>>> arg = 0xffff<<32
>>> # Kernel log: === (1234) Driver got arg ffff; arg<<32 is ffff00000000
>>> # This demonstrates that ioctl() happily accepts a 32-bit int as an argument.
>>> import struct
>>> ioctl(fd, 5678, struct.pack("L",arg))
'\x00\x00\x00\x00\xff\xff\x00\x00'
>>> # Kernel log: === (5678) Driver got arg 7fff9eb1fcb0
>>> # This demonstrates that ioctl() happily accepts a 64-bit pointer as an argument.
>>> ioctl(fd, 5678, arg)

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    ioctl(fd, 5678, arg)
OverflowError: signed integer is greater than maximum
>>> # Kernel log: (no change - OverflowError is within python)
>>> # Oh no! Can't pass a 64-bit int!
>>> 

Is there any way Python can pass my 64-bit argument to ioctl()?

Was it helpful?

Solution

Whether or not this is possible using Python's fcntl.ioctl() will be system-dependent. Tracing through the source code, the error message is coming from the following test on line 658 of getargs.c...

else if (ival > INT_MAX) {
    PyErr_SetString(PyExc_OverflowError,
    "signed integer is greater than maximum");
    RETURN_ERR_OCCURRED;
}

...and on my system, /usr/include/limits.h tells me...

#  define INT_MAX   2147483647

...which is (presumably) (2 ** ((sizeof(int) * 8) - 1)) - 1.

So, unless you're working on a system where sizeof(int) is at least 8, you'll have to call the underlying C function directly using the ctypes module, but it's platform-specific.

Assuming Linux, something like this ought to work...

from ctypes import *

libc = CDLL('libc.so.6')

fd = os.open (DEV_NAME, os.O_RDWR, 0666)
value = c_uint64(0xffff<<32)
libc.ioctl(fd, 5678, value)

OTHER TIPS

Notation of 'arg' in Python's ioctl is different from what of C.

In python (again according to 1) it either python integer (without specifying 32 or 64 bit), or a some sort of buffer object (like a string). You do not really have "pointers" in Python (so all underlaying architecture details - like 32 or 64 bit addresses are completely hidden).

If I understood correctly what you need is actually need for a SET_VAL is struct.pack(your 64-bit integer) into the string first and pass this string to ioctl, instead of passing of integer directly.

Like this:

struct.pack('>Q',1<<32)

For a GET_VAL you need a 'Q' type again (not a 'L') to unpack 64-bit integer value properly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top