4

I'm developing an application which needs to interact with Video4Linux abstraction. The application is developed in C#, using the mono framework.

The problem I'm facing is that I can't P/Invoke the ioctl system call. Or, more precisely, I can P/Invoke it, but it crashes badly.

The extern declaration is the following:

[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
private extern static int KernelIoCtrl(int fd, int request, IntPtr data);

So far so good.

The actual routine that uses the KernelIoCtrl is the following:

protected virtual int Control(IoSpecification request, object data)
{
    GCHandle dataHandle;
    IntPtr dataPointer = IntPtr.Zero;

    try {
        // Pin I/O control data
        if (data != null) {
            dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
            dataPointer = dataHandle.AddrOfPinnedObject();
        }
        // Perform I/O control
        int result =  KernelIoCtrl(mFileDescriptor, request.RequestCode, dataPointer);
        int errno = Marshal.GetLastWin32Error();

        // Throw exception on errors
        if (errno != (int)ErrNumber.NoError)
            throw new System.ComponentModel.Win32Exception(errno);

        return (result);
    } finally {
        if (dataPointer != IntPtr.Zero)
            dataHandle.Free();
    }
}

All the above code seems good. The class IoSpecification is used for computing the I/O request code following the header specification (basically it follows the _IOC macro declared at /usr/include/linux/asm/ioctl.h.

The data parameter is a structure, declared as follow:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Capability
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string Driver;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Device;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string BusInfo;

    public UInt32 Version;

    public CapabilityFlags Capabilities;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
    public UInt32[] Reserved;
}

which should mimic the following structure (declared at /usr/include/linux/videodev2.h):

struct v4l2_capability {
    __u8    driver[16];     /* i.e. "bttv" */
    __u8    card[32];       /* i.e. "Hauppauge WinTV" */
    __u8    bus_info[32];   /* "PCI:" + pci_name(pci_dev) */
    __u32   version;        /* should use KERNEL_VERSION() */
    __u32   capabilities;   /* Device capabilities */
    __u32   reserved[4];
};

Before having the crash, there was a problem in the IOCTL request code computation, and the KernelIoCtrl was working as expected (returning an errno equals to EINVAL). When I corrected the bug (and indeed having the correct IOCTRL request code), the call has started to cause a crash.

In conclusion, it seems there's a problem in the structure marshalling, but I cannot see what it's going wrong.

I fear that the problem is the variable argument list, because the ioctl routine is declared as follow (taken from man):

int ioctl(int d, int request, ...);

But I saw lot of code declaring the above routine as int ioctl(int d, int request, void*);, and I can ensure that the specific IOCTRL request takes only one argument.

Luca
  • 11,646
  • 11
  • 70
  • 125
  • I did something similar a while back and ended up with a different method declaration for each use of ioctl. Try: `private extern static int KernelIoCtrl(int fd, int request, ref Capability capability);` This should free you from doing the pinning stuff. If it still crashes, then your struct declaration is wrong. – dtb Apr 30 '12 at 17:21
  • @dtb Wow... it has worked! Is there a generic way to handle multiple IOCTL requests, without declaring a bunch of externs? There must be a way to do so! – Luca Apr 30 '12 at 17:28
  • Maybe there is some way to make it work, but I'd guess declaring the bunch of externs is easier and much more reliable. – dtb Apr 30 '12 at 17:32
  • Are you compiling as 32 or 64 bit? – Jonathan Henson Apr 30 '12 at 17:37
  • I've extracted some code from my old project that may help you. I'll post it as an answer, because it's too long for a comment. – dtb Apr 30 '12 at 17:42
  • @Jonathan I'm compiling and executing on 32 bit platform. – Luca Apr 30 '12 at 18:27
  • Luca, what filePath and request code did you use in your scenario ? I am [struggling](https://stackoverflow.com/q/68738429/1219280) to get an ioctl p/invoked call working. I would like to try your example with the exact same parameters you did, because the solution proposed here still returns -1 in my case (I am trying to read `/proc/net/dev` with request code `0x8B0B` (35595)) – Veverke Aug 11 '21 at 14:47

1 Answers1

4

Example usage:

Capability capability;

if (UnsafeNativeMethods.Ioctl(handle, request, ref capability) == -1)
{
    throw new UnixIOException();
}

Capability:

[StructLayout(LayoutKind.Sequential, Size = 104)]
internal struct Capability
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string Driver;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Device;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string BusInfo;

    public uint Version;

    public CapabilityFlags Capabilities;
}

UnsafeNativeMethods:

internal static class UnsafeNativeMethods
{
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [DllImport("libc", EntryPoint = "close", SetLastError = true)]
    internal static extern int Close(IntPtr handle);

    [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
    internal static extern int Ioctl(SafeUnixHandle handle, uint request, ref Capability capability);

    [DllImport("libc", EntryPoint = "open", SetLastError = true)]
    internal static extern SafeUnixHandle Open(string path, uint flag, int mode);

    internal static string Strerror(int error)
    {
        try
        {
            var buffer = new StringBuilder(256);
            var result = Strerror(error, buffer, (ulong)buffer.Capacity);
            return (result != -1) ? buffer.ToString() : null;
        }
        catch (EntryPointNotFoundException)
        {
            return null;
        }
    }

    [DllImport("MonoPosixHelper", EntryPoint = "Mono_Posix_Syscall_strerror_r", SetLastError = true)]
    private static extern int Strerror(int error, [Out] StringBuilder buffer, ulong length);
}

SafeUnixHandle:

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal sealed class SafeUnixHandle : SafeHandle
{
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    private SafeUnixHandle()
        : base(new IntPtr(-1), true)
    {
    }

    public override bool IsInvalid
    {
        get { return this.handle == new IntPtr(-1); }
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    protected override bool ReleaseHandle()
    {
        return UnsafeNativeMethods.Close(this.handle) != -1;
    }
}

UnixIOException:

[Serializable]
public class UnixIOException : ExternalException
{
    private readonly int nativeErrorCode;

    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    public UnixIOException()
        : this(Marshal.GetLastWin32Error())
    {
    }

    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    public UnixIOException(int error)
        : this(error, GetErrorMessage(error))
    {
    }

    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    public UnixIOException(string message)
        : this(Marshal.GetLastWin32Error(), message)
    {
    }

    public UnixIOException(int error, string message)
        : base(message)
    {
        this.nativeErrorCode = error;
    }

    public UnixIOException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    protected UnixIOException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        this.nativeErrorCode = info.GetInt32("NativeErrorCode");
    }

    public int NativeErrorCode
    {
        get { return this.nativeErrorCode; }
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
        {
            throw new ArgumentNullException("info");
        }

        info.AddValue("NativeErrorCode", this.nativeErrorCode);
        base.GetObjectData(info, context);
    }

    private static string GetErrorMessage(int error)
    {
        var errorDescription = UnsafeNativeMethods.Strerror(error);
        return errorDescription ?? string.Format("Unknown error (0x{0:x})", error);
    }
}
dtb
  • 213,145
  • 36
  • 401
  • 431
  • Your suggestion has worked, but I fear that it could not work because the structure memory is not pinned while P/Invoking. Apart that, I tried almost everything: unsafe extern (using void* arg), declaring the 3th arg as reference (as the typed delegate), using *__arglist* because the variadic argument declaration; everything has not worked. I think the last try (__arglist) is the correct one, while pinning the argument memory pointer. Sadly *mono* cannot handle it correctly (but it supports the *printf* routine!). – Luca May 01 '12 at 17:50
  • 1
    I know this is old, but what is `CapabilityFlags` property? – Andy Feb 27 '18 at 21:55