Search code examples
pythonpointersctypesfuse

CUSE IOCTL Reply with Struct Containing a Pointer to a Character Array


I am implementing a device driver in userspace in python with the help of ctypes and the CUSE part of FUSE. This is not my first CUSE driver, and in the past I have been able to do the following to return a structure.

class tvStruct(Structure):
    _fields_ = [("tv_sec",c_long),
                ("tv_usec",c_long)]

The timeval structure can be passed back to an application that uses an IOCTL call with the appropriate number. The following is the IOCTL file operation for the CUSE driver.

def ioctl(self, req, cmd, arg_p, file_info, uflags, in_buff_p, in_bufsz, out_bufsz):
    ioctl = DECODE_IOC(cmd)
    if ioctl == IOC_READ_TIMEVAL:
        if not in_buff_p:
            PyCuse.fuse_reply_ioctl_retry(req,
                                          pointer(PyCuse.iovec(cast(arg_p,c_void_p),
                                          sizeof(tvStruct))),1,
                                          pointer(PyCuse.iovec(cast(arg_p,c_void_p),
                                          sizeof(tvStruct))),1)
        else:
            tvPtr = cast(in_buff_p,POINTER(tvStruct))
            PyCuse.fuse_reply_ioctl(req, 0, tvPtr, sizeof(tvStruct))
    else:
        print("%s Unrecognized IOCTL #...\n",self.devname)
        PyCuse.fuse_reply_err(req,1)

The current driver I am developing needs to return a structure containing a pointer to a character array.

# C++ Struct
typedef struct {
    __u16 addr;
    __u16 length;
    __u8* pBuf;
} EEPromData;

# Python Class
class EEPromDataStruct(Structure):
    _fields_ = [("addr",c_ushort),
                ("length",c_ushort),
                ("pBuf",POINTER(c_char))] # I have tried several other ctypes types 
                                          # for `pBuf`, but to no avail yet

I understand that I can't assign a pointer from Python to pBuf because it is not the same memory context. I also understand that this is somewhat easy from kernel space with the use of copy_to_user. I am bound by requirements that this be a python application because it is attached to a python GUI. I appreciate any and all help.

Update 1

I know that this can be solved via the fuse_reply_ioctl_retry method. This method allows me to copy data from and to the application. The problem now is that upon a second call to fuse_reply_ioctl_retry, FUSE returns an error code to the application. I will continue trying to fix this, but would appreciate any help in the meantime. Thanks.


Solution

  • I have figured it out. The trick is to call fuse_reply_ioctl_retry for every pointer in the structure that you want to access the contents of, whether it be to modify them or just to read from them. This proved to be quite the hassle using python and ctypes along with FUSE/CUSE and if anyone else is doing this, I would strongly recommend switching to straight C for this application because ctypes can be tricky enough, but on top of CUSE/FUSE systems, it is pretty horrendous. I would have, but I was bound to python because this was an add-on to another application. Mini-rant over.

    Here is a stripped down version of the solution.

    # ctypes Structure for driver
    class EEPromDataStruct(Structure):
        _fields_ = [("addr",c_ushort),
                    ("length",c_ushort),
                    ("pBuf",c_ulong)] #u8*
                    # Note that this is a long instead of some pointer.
                    # This is so I can easily know the application address
                    # the pointer holds.
    ...
    
    # The IOCTL file operation for this driver
    def ioctl(self, req, cmd, arg_p, file_info, uflags, in_buff_p,
                                in_bufsz, out_bufsz):
        ioctl = DECODE_IOC(cmd)
        if ioctl == IOC_READ_EEPROM:
            if not in_buff_p:
                PyCuse.fuse_reply_ioctl_retry(req,pointer(PyCuse.iovec(
                                              cast(arg_p,c_void_p),sizeof(
                                              EEPromDataStruct))),1,None,0)
            else:
                if out_bufsz == 0:
                    eepromDataPtr = cast(in_buff_p,POINTER(EEPromDataStruct))
                    addr = eepromDataPtr.contents.addr
                    length = eepromDataPtr.contents.length
                    pBuf = eepromDataPtr.contents.pBuf
                    # Load from pBuf
                    out_iovecs = pointer(PyCuse.iovec(cast(pBuf,c_void_p),length))
                    in_iovecs = pointer(PyCuse.iovec(cast(pBuf,c_void_p),length))
                    PyCuse.fuse_reply_ioctl_retry(req,in_iovecs,1,out_iovecs,1)
                else:
                    eepromBuff.value = "Some String Here"
                    PyCuse.fuse_reply_ioctl(req, 0, eepromBuff, out_bufsz)
            else:
                print("%s Unrecognized IOCTL #...\n",self.devname)
                PyCuse.fuse_reply_err(req,1)
    

    if not in_buff_p will check if application memory has been loaded into the driver, and if not, it will call fuse_reply_ioctl_retry to do so. Note that one main difference now is that the output data vector specified for this retry was None. This is important because I then check to see what the output size is. If it is 0, then I know that it is my main EEPromDataStruct and I continue with that path. I prepare some data vectors and retry again. Finally, I take the pointer and assign some string to its contents.

    Its a lengthy process. But it works. I hope this will help anyone who needs it.