Я пишу код, который эмулирует один аспект pidfds на платформах, которые их не поддерживают (старый Linux, другой Unix).
Я делаю это а) для того, чтобы протестировать код, связанный с pidfd, на очень старых платформах и б) как личный вызов. В основном как личный вызов/для развлечения.
Я явно не пытаюсь заново реализовать все функции pidfd; На самом деле меня вообще не волнуют процессы//proc
/PID. Вместо этого я пытаюсь имитировать только одну часть возможностей pidfd: тот факт, что файловый дескриптор pidfd имеет три варианта поведения:
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
.
К сожалению, я хочу интегрироваться с библиотеками, которые принимают номер дескриптора файла, и я бы предпочел не прибегать, например. LD_PRELOAD
хаки для включения в новую версию read(2)
(и я не могу полагаться на динамическое связывание, даже если бы это было возможно).
На странице руководства 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
. Еще один хороший вариант.
Кроме того, как вы используете открытие с помощью O_DIRECT
? Проверьте Использование O_DIRECT в Linux для различных случаев и проблем.
Ах, извините, я мог бы быть яснее. Я имел в виду, что хочу передать этот файловый дескриптор API-интерфейсам, которые я не контролирую, которые принимают номер fd и вызывают read(2)
внутри компании, поэтому замена read()
моей собственной функцией не совсем идеальна. Обсуждения timerfd_create(2)
и O_DIRECT
актуальны только в том случае, если на странице руководства read(2)
эти системы упоминаются как способы заставить read()
вернуться EINVAL
. К сожалению, поведение timerfd
и O_DIRECT
, индуцированное EINVAL
, зависит от аргументов read()
, что не удовлетворяет моим требованиям ко всем read()
-вызовам, возвращающим EINVAL
.
Вызовите 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, чтобы его нельзя было использовать повторно? Если да, то это обязательно сработает. На самом деле, когда я попробовал это и какое-то время позволял своим тестам выполняться в присутствии других потоков/частей программы, тесты начали давать сбой, когда другие вещи повторно использовали номер дескриптора закрытого файла, и чтение стало успешным.
Нет, это приводит к EBADF
.
@JosephSible-ReinstateMonica, отредактировано...
Теперь это приводит к EFAULT
.
Нет, это привело к EINVAL
, если вы передали действительный буфер, по крайней мере, в моей системе.
Я добавил ссылку на ядро и его версию, поскольку, похоже, это проблема, зависящая от ОС.
@JosephSible-ReinstateMonica ИМХО, в обоих случаях вызов read()
один и тот же, пытается прочитать 18446744073709551606 байт памяти, но хотя FreeBSD напрямую выдает ошибку, Linux пытается их прочитать и терпит неудачу, когда указатель пытается скопировать все больше и больше Память. Странно, что оба вызова сразу завершаются неудачно, в обеих системах чтение не выполняется. Я знаю, что это UB (если я использую в качестве параметра -10, но это не так, если бы я попробовал значение выше)
Это полезная информация, но она все еще не совсем соответствует тому, к чему мне нужно: мне нужно, чтобы все (или большинство) допустимых в противном случае вызовов read(2)
, независимо от их аргументов, завершались неудачей с EINVAL
; не только вызовы с отрицательной длиной.
Случай 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 они получат пустую строку). В моем случае это достаточно редко, чтобы игнорировать.
Напишите свою собственную функцию
pidfd_read()
, которая обычно является просто оберткойread()
вместо прямого использованияread()
? Таким образом, вы можете контролировать то, что он возвращает при запуске тестов.