Linux splice() возвращает EINVAL (недопустимый аргумент)

Я пытаюсь поэкспериментировать с использованием вставки (man 2 splice) для копирования данных из сокета UDP непосредственно в файл. К сожалению, первый вызов splice() возвращает EINVAL.

На странице руководства указано:

EINVAL Target file system doesn't support splicing; target file is opened in
       append mode; neither of the descriptors refers to a pipe; or offset
       given for nonseekable device.

Однако я считаю, что ни одно из этих условий не применимо. Я использую Fedora 15 (ядро 2.6.40-4), поэтому я считаю, что splice() поддерживается во всех файловых системах. Целевой файл не должен иметь значения при первом вызове splice, но для полноты картины я открываю его через open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR). Оба вызова используют канал, и ни один из вызовов не использует смещение, кроме NULL.

Вот мой пример кода:

int sz = splice(sock_fd, 0, mPipeFds[1], 0, 8192, SPLICE_F_MORE);
if (-1 == sz)
{
int err = errno;
LOG4CXX_ERROR(spLogger, "splice from: " << strerror(err));
return 0;
}

sz = splice(mPipeFds[0], 0, file_fd, 0, sz, SPLICE_F_MORE);
if (-1 == sz)
{
int err = errno;
LOG4CXX_ERROR(spLogger, "splice to: " << strerror(err));
}

return 0;

sock_fd инициализируется следующим псевдокодом:

int sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
bind(sock_fd, ...);

Возможно, это связано с тем, что этот фрагмент кода выполняется внутри цикла libevent. libevent использует epoll(), чтобы определить, горячий ли сокет UDP.


person bfallik-bamboom    schedule 16.08.2011    source источник
comment
sock_fd инициализация выглядит ужасно. Проверьте возвращаемые значения!   -  person Karoly Horvath    schedule 17.08.2011
comment
Это просто псевдокод. Фактический код делает. Я немного уточнил текст.   -  person bfallik-bamboom    schedule 17.08.2011


Ответы (2)


Нашел свой ответ. tl;dr — UDP не поддерживается на входящей стороне.

После долгих поисков в Google я наткнулся на обсуждение на форуме и несколько тестовый код, который выводит таблицу входных/выходных типов fd и их поддержку:

$ ./a.out 
in\out     pipe    reg     chr     unix    tcp    udp
pipe       yes     yes     yes     yes     yes    yes
reg        yes     no      no      no      no     no
chr        yes     no      no      no      no     no
unix       no      no      no      no      no     no
tcp        yes     no      no      no      no     no
udp        no      no      no      no      no     no
person bfallik-bamboom    schedule 17.08.2011
comment
Было бы очень хорошо, если бы этот код можно было воспроизвести здесь, чтобы он не потерялся навсегда. - person Aktau; 31.03.2016
comment
Предположим, что он поддерживается, как он будет работать? Как узнать, откуда пришла дейтаграмма UDP? В сокете нет соединения, поэтому оно могло прийти из любого места в Интернете. - person Damon; 03.09.2019

Ага, точно не поддерживается чтение из сокета UDP даже в последних ядрах. Далее следуют ссылки на исходный код ядра.

splice вызывает do_splice в ядре, которое вызывает do_splice_to, который вызывает член splice_read в структуре file_operations для файла.

Для сокетов эта структура определяется как socket_file_ops в net/socket.c., который инициализирует поле splice_read значением sock_splice_read.

Эта функция, в свою очередь, содержит следующую строку кода:

if (unlikely(!sock->ops->splice_read))
    return -EINVAL;

Поле ops сокета представляет собой struct proto_ops. Для сокета UDP IPv4 он инициализируется как inet_dgram_ops в сети /ipv4/af_inet.c. Наконец, эта структура не инициализирует явно поле splice_read в struct proto_ops; т. е. инициализирует его нулем.

Итак, sock_splice_read возвращает -EINVAL, и это распространяется вверх.

person Nemo    schedule 17.08.2011