javalinux-device-driverinputstreaminput-devices

IOException when reading few bytes from /dev/input


When trying to read input_events from /dev/input/event16 I noticed that the size of the buffer I'm reading into may cause an exception. Here is the code I wrote:

    public static void main(String[] args) throws IOException{
        FileInputStream is = new FileInputStream("/dev/input/event16");
        byte[] three_bytes = new byte[3];
        byte[] twentyfour_bytes = new byte[24];
        is.read(three_bytes); // fails
        is.read(twentyfour_bytes); // does not fail

    }

My initial experiments suggest that the buffer needs capacity for at least one full input_event struct. But I could not find out why.

The problem is the line is.read(three_bytes); causes the follwoing exception:

Exception in thread "main" java.io.IOException: Invalid argument
        at java.base/java.io.FileInputStream.readBytes(Native Method)
        at java.base/java.io.FileInputStream.read(FileInputStream.java:249)
        at main.Test.main(Test.java:11)

I would like to figure out why the line is.read(three_bytes); throws the exception while is.read(twentyfour_bytes); reads the data as expected


Solution

  • I would like to figure out why the line is.read(three_bytes); throws the exception while is.read(twentyfour_bytes); reads the data as expected.

    For a start, /dev/input/event16 is not a regular file. It is a device file, and device files often don't behave like regular files.

    In this case, the /dev/input/event* device files are for reading events from input devices. When you perform a read syscall on them, they will return one or more complete events. These are binary data whose format is given by the following C struct:

    struct input_event {    
        struct timeval time;    
        unsigned short type;
        unsigned short code;
        unsigned int value; 
    };
    

    The size of that struct is (presumably) 24 bytes on a typical 64bit Linux system, though.

    My initial experiments suggest that the buffer needs capacity for at least one full input_event struct. But I could not find out why.

    The behavior of the event devices is documented in Linux Input drivers v1.0, section 5. It states that a read will always give a whole number of input_event structs.

    It follows that the read syscall provides a buffer that is less than sizeof(input_event), the kernel cannot return anything. Apparently, this causes the read syscall to fail with errno value EINVAL. (IMO, this is a reasonable design choice, and consistent with the documented meaning of EINVAL.)


    So, in Java, when you call read(three_bytes), that will map to a read syscall with a read size of 3 bytes which fails; see above. The syscall failure is signaled to the Java application by throw an IOException.