5

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()?

Ziv
  • 2,369
  • 3
  • 24
  • 40
  • Would help to provide a reproducible example, if possible. Given that `ioctl()` calls are device-specific, substituting `IOC_GET_VAL` for the actual request code you're using makes this difficult to test. – Aya Jun 23 '13 at 18:48
  • @Aya: Thanks for the comment. I'm new to device drivers, and having a bit of trouble constructing a trivial-yet-functional example. But I'll see what I can do. :) – Ziv Jun 23 '13 at 19:01
  • In the meantime, I've posted a `ctypes`-based solution. – Aya Jun 23 '13 at 19:37
  • OK, I got around to posting a clearer example! I'll puzzle over the answers now... – Ziv Jun 26 '13 at 18:09
  • Leaving the bounty open a couple days more just in case, but barring a new, more pythonic solution, it'll go to Aya. :) – Ziv Jun 26 '13 at 18:33
  • 1
    I found a simpler way to test, using `strace(1)`, and after having re-checked Python's source code, it's probably impossible using the `fcntl` module. See also my updated answer. – Aya Jun 28 '13 at 16:02

2 Answers2

6

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)
Aya
  • 39,884
  • 6
  • 55
  • 55
  • Yes! This works :) I'm surprised there's no built-in support inside fcntl.ioctl(), but this is a perfectly reasonable workaround. – Ziv Jun 26 '13 at 18:27
  • cf. http://stackoverflow.com/questions/9257065/int-max-in-32-bit-vs-64-bit-environment . – Ziv Jun 28 '13 at 16:18
  • @Ziv Yeah. I was under the impression (for some reason) that an `int` was supposed to be the same size as a CPU register, but it's not. – Aya Jun 28 '13 at 16:23
3

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.

Georgiy
  • 99
  • 5
  • I'm sorry, this isn't correct. (Sorry if my original post wasn't clear enough.) As far as I can tell, **any** use of struct.pack() translates (within the c driver) into a pointer to binary data being sent to c's ioctl argument. I don't want a pointer; I want to directly control the 64-bit value being passed. – Ziv Jun 26 '13 at 18:16
  • But strictly speaking, you can not really pass any 64 bit value via 32-bit arguments (being it a pointer or a 'value'). And type of parameter in ioctl call will be a 32bit in 32-bit architecture. On 64-bit systems everything became 64bit, so theoretically you can pass 64-bit values to IOCTL **in C***. – Georgiy Jul 01 '13 at 04:31
  • Python in other hand tries to hide any of these platform specifics, so it does no expose any pointers-API. Idea is you must never care what architecture is. In fact Python's integers can be arbitrary big (i.e. not limited by 64bit or whatever...) So to remain _Pythonic_ (and platform-independent) it is probably better to pass buffer-objects even for little things like a 64-bit integers... – Georgiy Jul 01 '13 at 04:40
  • Yes, but that's *not how this particular, existing ioctl call is implemented.* ioctl's argument can be treated as a pointer *or* as an integer; that's why python gives the option of passing an integer to begin with. – Ziv Jul 01 '13 at 07:36