inotify 文件描述符上的 os.read:读取 32 字节有效但 31 引发异常

Mic*_*mza 0 linux ctypes inotify python-3.x python-asyncio

我正在编写一个应该使用 inotify 响应文件更改的程序。下面的骨架程序按我的预期工作......

# 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))
Run Code Online (Sandbox Code Playgroud)

...因为它输出...

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'
Run Code Online (Sandbox Code Playgroud)

但是,如果我将其更改为尝试读取 31 个字节...

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'
Run Code Online (Sandbox Code Playgroud)

......然后它引发了一个异常......

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
Run Code Online (Sandbox Code Playgroud)

...对于我尝试过的所有小于 31 的数字,包括 1 个字节,类似。

为什么是这样?我原以为它应该能够尝试读取任意数量的字节,并且只返回缓冲区中的任何内容,直到os.read.


我在 Mac OS 上的 docker 容器中的 Alpine linux 3.10 中运行它,使用非常基本的 Dockerfile:

FROM alpine:3.10
RUN apk add --no-cache python3
COPY test.py /
Run Code Online (Sandbox Code Playgroud)

并运行它

event_bytes = os.read(fd, 31)
Run Code Online (Sandbox Code Playgroud)

Mic*_*mza 5

这是因为它被写入只允许读取可以返回有关下一个事件的信息。来自http://man7.org/linux/man-pages/man7/inotify.7.html

当提供给 read(2) 的缓冲区太小而无法返回有关下一个事件的信息时的行为取决于内核版本:在 2.6.21 之前的内核中,read(2) 返回 0;从内核 2.6.21 开始,read(2) 失败并显示错误 EINVAL。

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

#define EINVAL      22  /* Invalid argument */
Run Code Online (Sandbox Code Playgroud)

这大概映射到 PythonOSErrorErrno 22.