Как я могу заставить вызов read(2) вернуть EINVAL?

Контекст

Я пишу код, который эмулирует один аспект pidfds на платформах, которые их не поддерживают (старый Linux, другой Unix).

Я делаю это а) для того, чтобы протестировать код, связанный с pidfd, на очень старых платформах и б) как личный вызов. В основном как личный вызов/для развлечения.

Я явно не пытаюсь заново реализовать все функции pidfd; На самом деле меня вообще не волнуют процессы//proc/PID. Вместо этого я пытаюсь имитировать только одну часть возможностей pidfd: тот факт, что файловый дескриптор pidfd имеет три варианта поведения:

  1. Если открыт в режиме блокировки, блокируется при чтении.
  2. Если он открыт или переведен в режим без блокировки, верните EWOULDBLOCK при чтении.
  3. Явно возвращайте fcntl при чтении.

Эта третья часть — самая сложная часть и необычный аспект pidfds на данный момент (очень мало что намеренно возвращает EINVAL вместо EINVAL).

Вопрос

Я хочу вызвать такое поведение в каком-то другом файловом дескрипторе (неважно, в каком). В частности, мне нужен дескриптор файла, который:

  • По умолчанию подчиняется обычному read() (или нет) поведению.
  • После того, как я что-то с ним сделаю, все вызовы O_NONBLOCK, которые обычно не возвращают ошибку, вместо этого вернут read(2), независимо от параметров EINVAL.

Это оказывается на удивление сложно.

Что я пробовал

На странице руководства read(2) для read(2) написано, что она возвращается, если:

fd привязан к объекту, непригодному для чтения; или файл был открыт с флагом O_DIRECT, и либо адрес указано в buf, значение, указанное в count, или смещение файла равно не выровнены должным образом.

...или если в EINVAL по таймеру передан неверный размер буфера.

Ни случай timerfd, ни случай read(2) не удовлетворяют моим требованиям, поскольку они возвращают O_DIRECT только в том случае, если в EINVAL передаются определенные аргументы, и я хочу, чтобы он возвращался во всех случаях без ошибок.

Я также пробовал signalfds (не смог найти случай, который возвращал EINVAL при чтении), inotify FD (тот же) и различные варианты принудительного read(2)d или close(2) каналов, FIFO и анонимных сокетов.

Однако я не очень хорошо разбираюсь в мелочах POSIX, поэтому вполне возможно, что я пропустил что-то, что позволяет возвращать тип файлового дескриптора, над которым я уже экспериментировал shutdown(2).

Бонусные баллы, если есть решение, которое работает на BSD/MacOS, но на самом деле все лучше, чем ничего, даже если оно зависит от Linux или версии ядра.

Я пробовал некоторые другие трюки по этому вопросу, но они в основном генерируют коды ошибок, отличные от EINVAL.

Напишите свою собственную функцию pidfd_read(), которая обычно является просто оберткой read() вместо прямого использования read()? Таким образом, вы можете контролировать то, что он возвращает при запуске тестов.

Shawn 05.07.2024 21:59

К сожалению, я хочу интегрироваться с библиотеками, которые принимают номер дескриптора файла, и я бы предпочел не прибегать, например. LD_PRELOAD хаки для включения в новую версию read(2) (и я не могу полагаться на динамическое связывание, даже если бы это было возможно).

Zac B 05.07.2024 22:16

На странице руководства read(2) EINVAL также написано "fd was created via a call to timerfd_create(2) and the wrong size buffer was given to read(); see timerfd_create(2) for further information." — вы следили за этим? Кроме того, я думаю, что @Shawn предлагал вам просто написать оболочку для read, а не динамически загружать что-то, уже являющееся частью другой библиотеки. Таким образом, вы контролируете, при каких обстоятельствах ваша оболочка возвращается EINVAL с учетом параметров и любого тестирования, которое вы хотите провести, чтобы прийти к обстоятельствам, которые вы хотите вернуть EINVAL. Еще один хороший вариант.

David C. Rankin 06.07.2024 01:02

Кроме того, как вы используете открытие с помощью O_DIRECT? Проверьте Использование O_DIRECT в Linux для различных случаев и проблем.

David C. Rankin 06.07.2024 01:13

Ах, извините, я мог бы быть яснее. Я имел в виду, что хочу передать этот файловый дескриптор API-интерфейсам, которые я не контролирую, которые принимают номер fd и вызывают read(2) внутри компании, поэтому замена read() моей собственной функцией не совсем идеальна. Обсуждения timerfd_create(2) и O_DIRECT актуальны только в том случае, если на странице руководства read(2) эти системы упоминаются как способы заставить read() вернуться EINVAL. К сожалению, поведение timerfd и O_DIRECT, индуцированное EINVAL, зависит от аргументов read(), что не удовлетворяет моим требованиям ко всем read()-вызовам, возвращающим EINVAL.

Zac B 06.07.2024 02:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вызовите read() с отрицательным размером, например:

$ cat >so_$$.c
#include <stdio.h>
#include <unistd.h>

char buffer[100];

int main()
{
    int bytes_read = read(0, buffer, -10);
    if (bytes_read < 0) {
        perror("read");
    }
}
$ make so_$$
cc -O2 -pipe  so_43241.c  -o so_43241
$ ./so_$$
read: Invalid argument
$ uname -a
FreeBSD europa.lcssl.es 14.0-STABLE FreeBSD 14.0-STABLE #10 stable/14-n266056-70025e767f28: Wed Dec 27 11:41:52 EET 2023     [email protected]:/home/lcu/obj/home/usr/src/amd64.amd64/sys/GENERIC amd64
$ _

Есть ли способ зарезервировать закрытый номер FD, чтобы его нельзя было использовать повторно? Если да, то это обязательно сработает. На самом деле, когда я попробовал это и какое-то время позволял своим тестам выполняться в присутствии других потоков/частей программы, тесты начали давать сбой, когда другие вещи повторно использовали номер дескриптора закрытого файла, и чтение стало успешным.

Zac B 08.07.2024 14:23

Нет, это приводит к EBADF.

Joseph Sible-Reinstate Monica 09.07.2024 01:42

@JosephSible-ReinstateMonica, отредактировано...

Luis Colorado 09.07.2024 09:13

Теперь это приводит к EFAULT.

Joseph Sible-Reinstate Monica 09.07.2024 16:01

Нет, это привело к EINVAL, если вы передали действительный буфер, по крайней мере, в моей системе.

Luis Colorado 18.07.2024 14:03

Я добавил ссылку на ядро ​​и его версию, поскольку, похоже, это проблема, зависящая от ОС.

Luis Colorado 18.07.2024 14:13

@JosephSible-ReinstateMonica ИМХО, в обоих случаях вызов read() один и тот же, пытается прочитать 18446744073709551606 байт памяти, но хотя FreeBSD напрямую выдает ошибку, Linux пытается их прочитать и терпит неудачу, когда указатель пытается скопировать все больше и больше Память. Странно, что оба вызова сразу завершаются неудачно, в обеих системах чтение не выполняется. Я знаю, что это UB (если я использую в качестве параметра -10, но это не так, если бы я попробовал значение выше)

Luis Colorado 18.07.2024 14:23

Это полезная информация, но она все еще не совсем соответствует тому, к чему мне нужно: мне нужно, чтобы все (или большинство) допустимых в противном случае вызовов read(2), независимо от их аргументов, завершались неудачей с EINVAL; не только вызовы с отрицательной длиной.

Zac B 26.07.2024 22:20
Ответ принят как подходящий

Случай O_DIRECT действительно может удовлетворить ваши требования, поскольку смещение файла не является одним из аргументов read. Если вы это сделаете int fd = open("/bin/sh", O_RDONLY|O_DIRECT); lseek(fd, 1, SEEK_SET);, то read(fd, buf, count); должен потерпеть неудачу с EINVAL независимо от того, что такое buf и count.

Спасибо! Одно замечание: O_DIRECT работает таким образом только для блочных файлов, но (не)блокирующая семантика pidfd подобна сокету, а не блочному файлу. Чтобы обойти это, я создаю обычную пару pipe(2), возвращаю ее прочитанный конец пользователю, а затем, когда происходит выход PID, использую dup2 для замены канала чтения искомым O_DIRECT простым файлом FD, как предложено здесь. Затем я закрываю канал записи, чтобы активировать любой выбор/(e)опрос/и т.д. Это работает, если код пользователя не заблокирован в read(2) (в этом случае вместо EINVAL они получат пустую строку). В моем случае это достаточно редко, чтобы игнорировать.

Zac B 27.07.2024 02:01

Другие вопросы по теме