os.read on inotify file descriptor: reading 32 bytes works but 31 raises an exception

0

I'm writing a program that should respond to file changes using inotify. The below skeleton program works as I expect...

# test.py
import asyncio
import ctypes
import os

IN_CLOSE_WRITE = 0x00000008

async def main(loop):
    libc = ctypes.cdll.LoadLibrary('libc.so.6')
    fd = libc.inotify_init()

    os.mkdir('directory-to-watch')
    wd = libc.inotify_add_watch(fd, 'directory-to-watch'.encode('utf-8'), IN_CLOSE_WRITE)
    loop.add_reader(fd, handle, fd)

    with open(f'directory-to-watch/file', 'wb') as file:
        pass

def handle(fd):
    event_bytes = os.read(fd, 32)
    print(event_bytes)

loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))

... in that it outputs...

b'\x01\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00file\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

However, if I change it to attempt to read 31 bytes...

event_bytes = os.read(fd, 31)

... then it raises an exception...

Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/t.py", line 19, in handle
    event_bytes = os.read(fd, 31)
OSError: [Errno 22] Invalid argument

... and similarly for all numbers smaller than 31 that I have tried, including 1 byte.

Why is this? I would have thought it should be able to attempt to read any number of bytes, and just return whatever is in the buffer, up to the length given by the second argument of os.read.


I'm running this in Alpine linux 3.10 in a docker container on Mac OS, with very basic Dockerfile:

FROM alpine:3.10
RUN apk add --no-cache python3
COPY test.py /

and running it by

docker build . -t test && docker run -it --rm test python3 /test.py
linux
python-3.x
ctypes
python-asyncio
inotify
asked on Stack Overflow Jul 21, 2019 by Michal Charemza

1 Answer

5

It's because it's written to only allow reads that can return information about the next event. From http://man7.org/linux/man-pages/man7/inotify.7.html

The behavior when the buffer given to read(2) is too small to return information about the next event depends on the kernel version: in kernels before 2.6.21, read(2) returns 0; since kernel 2.6.21, read(2) fails with the error EINVAL.

and from https://github.com/torvalds/linux/blob/f1a3b43cc1f50c6ee5ba582f2025db3dea891208/include/uapi/asm-generic/errno-base.h#L26

#define EINVAL      22  /* Invalid argument */

which presumably maps to the Python OSError with Errno 22.

answered on Stack Overflow Jul 21, 2019 by Michal Charemza

User contributions licensed under CC BY-SA 3.0