What is the equivalent of the C _IOR function in Python?

1.4k Views Asked by At

I was recently looking at some C code and 'translating' to Python but got stuck at a particular function called _IOR. It is defined in sys/ioctl.h like so:

#define _IOC(inout,group,num,len) \
    (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))
#define _IOR(g,n,t) _IOC(IOC_OUT,   (g), (n), sizeof(t))

And I have seen it called in these ways:

_IOR('t', 3, int)
_IOR('keys', 1, unsigned char *)

The 'keys' call is the one I need to do. Looks like it's doing a bitwise operation on a string.

I managed to find equivalent Python code to the above but it only works for a single character.

_IOC_NRBITS   =  8
_IOC_TYPEBITS =  8
_IOC_SIZEBITS = 14
_IOC_DIRBITS  =  2

_IOC_NRSHIFT = 0
_IOC_TYPESHIFT =(_IOC_NRSHIFT+_IOC_NRBITS)
_IOC_SIZESHIFT =(_IOC_TYPESHIFT+_IOC_TYPEBITS)
_IOC_DIRSHIFT  =(_IOC_SIZESHIFT+_IOC_SIZEBITS)

_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ = 2
def _IOC(direction,type,nr,size):
    return (((direction)  << _IOC_DIRSHIFT) |
        ((type) << _IOC_TYPESHIFT) |
        ((nr)   << _IOC_NRSHIFT) |
        ((size) << _IOC_SIZESHIFT))
def _IOR(type, number, size):
    return _IOC(_IOC_READ, type, number, size)

This works for single character calls.

_IOR(ord('t'), 3, 1)

But I don't know the equivalent of the second call with 'keys'. Is there a way to do this C call below in Python?

_IOR('keys', 1, unsigned char *)
2

There are 2 best solutions below

1
On

The number you're getting from running on a char* isn't guaranteed to be any specific number. The macro is treating the address of the string as an integer when computing _IOR for keys. The result will change depending on where keys lives in the stack. As a result there's really not a good replacement for this functionality in Python.

From the docs for _IOR(g, n, t):

g
Specifies the group that this ioctl type belongs to. This argument must be a nonnegative 8-bit number (that is, in the range 0-255 inclusive). You can pass the value zero (0) to this argument if a new ioctl group is not being defined.

The intended use of the function is to take a byte as the first argument (via character or integer types). That being said, 'keys' was probably getting assigned to some random group. I would pick a group which you think won't be already taken (or go lookup the group it's currently be assigned to in your C code and use that number) in place of keys.

Alternatively you could always just take the first character of the input string and treat that as the group id.

def _IOR(group, number, size):
    if isinstance(group, basestring):
        group = ord(group[0]) if group else 0
    return _IOC(_IOC_READ, group, number, size)

Then _IOR('t', 3, 1) and _IOR('keys', 1, 1) would both still run, though the group assignments won't be identical to the C macro. But since the macro was being misused for the keys assignment I don't think this will be a problem.

0
On

'keys' is an integer character constant (containing four characters), the value of which is implementation-defined. You stated that the value returned by the C code _IOR('keys', 1, unsigned char *) is -444763391, which is, as a 4 byte hex number, 0xE57D7301. From that we can conclude that this implementation computes the integer value 0x6B657973 of 'keys' from 'k'<<24|'e'<<16|'y'<<8|'s'. The _IOR value 0xE57D7301 results from the expression

2<<30|4<<16|'keys'<<8|1
:     :     :         :
:     :     :         1 = num n
:     :     'keys' = group g
:     4 = sizeof (unsigned char *)
2 = _IOC_READ

- note that only 8 Bits are reserved for a TYPE (group) value, and this creative use of a 32 bit value makes the TYPE overflow into the SIZE and DIR bits (getting ORed with them) and its most significant byte (resulting from 'k'<<24) even gets shifted out of the int value.

With your Python function you can compute the same value by calling

_IOR(ord('e')<<16|ord('y')<<8|ord('s'), 1, 4)

(since the k gets lost, we can drop it outright).