How to use ioctl + nix macros to get a variable size buffer

1.3k views Asked by At

This is related to How to use nix's ioctl? but it is not the same question.

I want to retrieve a variable size buffer. There is another ioctl that tells me that I need to read X bytes. The C header tells me the following too:

#define HID_MAX_DESCRIPTOR_SIZE     4096
#define HIDIOCGRDESC        _IOR('H', 0x02, struct hidraw_report_descriptor)

struct hidraw_report_descriptor {
    __u32 size;
    __u8 value[HID_MAX_DESCRIPTOR_SIZE];
};

I define the macro in the following way:

ioctl_read_buf!(hid_read_descr, b'H', 0x02, u8);

And later call:

let mut desc_raw = [0u8; 4 + 4096];
let err = unsafe { hid_read_descr(file.as_raw_fd(), &mut desc_raw); };

When doing this, desc_raw is full of zeros. I would have expected the first 4 bytes to contain size based on the struct definition.

The alternative, does not seem to work either

ioctl_read!(hid_read_descr2, b'H', 0x02, [u8; 4+4096]);
// ...
let mut desc_raw = [0xFFu8; 4 + 4096];
let err = unsafe { hid_read_descr2(file.as_raw_fd(), &mut desc_raw); };

In both cases, I have tried initializing desc_raw with 0xFF and after the call, it seems untouched.

Am I using the ioctl_read_buf macro incorrectly?

2

There are 2 answers

1
Shepmaster On BEST ANSWER

Now that Digikata has thoughtfully provided enough code to drive the program...

Am I using the ioctl_read_buf macro incorrectly?

I'd say that using it at all is incorrect here. You don't want to read an array of data, you want to read a single instance of a specific type. That's what ioctl_read! is for.

We define a repr(C) struct that mimics the C definition. This ensures that important details like alignment, padding, field ordering, etc., all match one-to-one with the code we are calling.

We can then construct an uninitialized instance of this struct and pass it to the newly-defined function.

use libc; // 0.2.66
use nix::ioctl_read; // 0.16.1
use std::{
    fs::OpenOptions,
    mem::MaybeUninit,
    os::unix::{fs::OpenOptionsExt, io::AsRawFd},
};

const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;

#[repr(C)]
pub struct hidraw_report_descriptor {
    size: u32,
    value: [u8; HID_MAX_DESCRIPTOR_SIZE],
}

ioctl_read!(hid_read_sz, b'H', 0x01, libc::c_int);
ioctl_read!(hid_read_descr, b'H', 0x02, hidraw_report_descriptor);

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .custom_flags(libc::O_NONBLOCK)
        .open("/dev/hidraw0")?;

    unsafe {
        let fd = file.as_raw_fd();

        let mut size = 0;
        hid_read_sz(fd, &mut size)?;
        println!("{}", size);

        let mut desc_raw = MaybeUninit::<hidraw_report_descriptor>::uninit();
        (*desc_raw.as_mut_ptr()).size = size as u32;
        hid_read_descr(file.as_raw_fd(), desc_raw.as_mut_ptr())?;
        let desc_raw = desc_raw.assume_init();
        let data = &desc_raw.value[..desc_raw.size as usize];
        println!("{:02x?}", data);
    }

    Ok(())
}
3
Digikata On

I think you've got a couple of issues here. Some on the Rust side, and some with using the HIDIOCGRDESC ioctl incorrectly. If you look in a Linux kernel distribution at the hidraw.txt and hid-example.c code, the use of the struct is as follows:

struct hidraw_report_descriptor rpt_desc;

memset(&rpt_desc, 0x0, sizeof(rpt_desc));

/* Get Report Descriptor */
rpt_desc.size = desc_size;
res = ioctl(fd, HIDIOCGRDESC, &rpt_desc);

desc_size comes from a previous HIDIOCGRDESCSIZE ioctl call. Unless I fill in the correct size parameter, the ioctl returns an error (ENOTTY or EINVAL).

There are also issues with passing the O_NONBLOCK flag to open a HID device without using libc::open. I ended up with this:

#[macro_use]
extern crate nix;

extern crate libc;

ioctl_read!(hid_read_sz, b'H', 0x01, i32);
ioctl_read_buf!(hid_read_descr, b'H', 0x02, u8);

fn main() {
    // see /usr/include/linux/hidraw.h
    // and hid-example.c
    extern crate ffi;
    use std::ffi::CString;
    let fname = CString::new("/dev/hidraw0").unwrap();
    let fd = unsafe { libc::open(fname.as_ptr(), libc::O_NONBLOCK | libc::O_RDWR) };

    let mut sz = 0i32;
    let err = unsafe { hid_read_sz(fd, &mut sz) };
    println!("{:?} size is {:?}", err, sz);

    let mut desc_raw = [0x0u8; 4 + 4096];

    // sz on my system ended up as 52 - this handjams in the value
    // w/ a little endian swizzle into the C struct .size field, but
    // really we should properly define the struct
    desc_raw[0] = sz as u8;

    let err = unsafe { hid_read_descr(fd, &mut desc_raw) };
    println!("{:?}", err);

    for (i, &b) in desc_raw.iter().enumerate() {
        if b != 0 {
            println!("{:4} {:?}", i, b);
        }
    }
}

In the end, you shouldn't be sizing the struct to a variable size, the ioctl header indicates there is a fixed max expected. The variability is all on the system ioctl to deal with, it just needs the expected size hint from another ioctl call.