fgets()
предназначался для чтения некоторой строки до тех пор, пока не произойдет EOF
или \n
. Например, это очень удобно для чтения текстовых конфигурационных файлов, но есть некоторые проблемы.
Во-первых, он может вернуть EINTR
в случае доставки сигнала, поэтому его следует обернуть проверкой цикла.
Вторая проблема гораздо хуже: по крайней мере, в glibc он вернет EINTR
и потеряет все уже прочитанные данные, если они доставлены посередине. Это вряд ли произойдет, но я думаю, что это может быть причиной некоторых сложных уязвимостей в некоторых демонах.
Установка флага SA_RESTART
для сигналов, похоже, помогает избежать этой проблемы, но я не уверен, что он охватывает ВСЕ возможные случаи на всех платформах. Это 1013 *
Если нет, есть ли способ вообще избежать этой проблемы?
Если нет, похоже, что fgets()
не может использоваться для чтения файлов в демонах, поскольку это может привести к случайной потере данных.
Пример кода для тестов:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
static char buf[1000000];
static volatile int do_exit = 0;
static void int_sig_handle(int signum) { do_exit = 1; }
void try(void) {
char * r;
int err1, err2;
size_t len;
memset(buf,1,20); buf[20]=0;
r = fgets(buf, sizeof(buf), stdin);
if(!r) {
err1 = errno;
err2 = ferror(stdin);
printf("\n\nfgets()=NULL, errno=%d(%s), ferror()=%d\n", err1, strerror(err1), err2);
len = strlen(buf);
printf("strlen()=%u, buf=[[[%s]]]\n", (unsigned)len, buf);
} else if(r==buf) {
err1 = errno;
err2 = ferror(stdin);
len = strlen(buf);
if(!len) {
printf("\n\nfgets()=buf, strlen()=0, errno=%d(%s), ferror()=%d\n", err1, strerror(err1), err2);
} else {
printf("\n\nfgets()=buf, strlen()=%u, [len-1]=0x%02X, errno=%d(%s), ferror()=%d\n",
(unsigned)len, (unsigned char)(buf[len-1]), err1, strerror(err1), err2);
}
} else {
printf("\n\nerr\n");
}
}
int main(int argc, char * * argv) {
struct sigaction sa;
sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = int_sig_handle;
sigaction(SIGINT, &sa, NULL);
printf("attempt 1\n");
try();
printf("\nattempt 2\n");
try();
printf("\nend\n");
return 0;
}
Этот код можно использовать для проверки доставки сигнала в середине "попытки 1" и обеспечения того, чтобы его частично считанные данные после этого полностью терялись.
Как проверить:
- запустить программу с помощью strace
- введите некоторую строку (не нажимайте Enter), нажмите Ctrl + D, см.
read()
системный вызов завершен с некоторыми данными
- отправить
SIGINT
- см.
fread()
вернул NULL
, "попытка 2" и введите некоторые данные и нажмите Enter
- он напечатает вторые введенные данные, но нигде не напечатает первый
FreeBSD 11 libc: то же поведение
FreeBSD 8 libc: первая попытка возвращает частично прочитанные данные и устанавливает ferror () и errno
РЕДАКТИРОВАТЬ: в соответствии с рекомендациями @John Bollinger Я добавил дамп буфера после возврата NULL. Результаты:
glibc и FreeBSD 11 libc: buffer содержит эти частично прочитанные данные, но НЕ NULL-TERM, поэтому единственный способ получить его длину - очистить весь буфер перед вызовом fgets (), который выглядит не так, как предполагалось
FreeBSD 8 libc: по-прежнему возвращает правильно прочитанные данные с нулевым символом в конце