Почему циклу фреда требуется дополнительный Ctrl + D, чтобы сигнализировать EOF с помощью glibc? - PullRequest
0 голосов
/ 06 октября 2018

Обычно, чтобы указать EOF для программы, подключенной к стандартному вводу на терминале Linux, мне нужно нажать Ctrl + D один раз, если я только что нажал Enter, или два раза в противном случае.Я заметил, что команда patch отличается.При этом мне нужно дважды нажать Ctrl + D, если я только что нажал Enter, или три раза в противном случае.(Выполнение cat | patch вместо этого не имеет этой странности. Кроме того, если я нажимаю Ctrl + D перед тем, как вообще вводить какой-либо реальный ввод, у него нет этой странности.) Копаясь в исходном коде patch, я проследилэто возвращается к , как это происходит на fread.Вот минимальная программа, которая делает то же самое:

#include <stdio.h>

int main(void) {
    char buf[4096];
    size_t charsread;
    while((charsread = fread(buf, 1, sizeof(buf), stdin)) != 0) {
        printf("Read %zu bytes. EOF: %d. Error: %d.\n", charsread, feof(stdin), ferror(stdin));
    }
    printf("Read zero bytes. EOF: %d. Error: %d. Exiting.\n", feof(stdin), ferror(stdin));
    return 0;
}

При компиляции и запуске вышеуказанной программы в том виде, в каком она есть, вот график событий:

  1. Моя программа вызывает fread.
  2. fread вызывает системный вызов read.
  3. Я набираю «asdf».
  4. Я нажимаю Enter.
  5. read системный вызов возвращает 5.
  6. fread снова вызывает системный вызов read.
  7. Я нажимаю Ctrl + D.
  8. Системный вызов read возвращает0.
  9. fread возвращает 5.
  10. Моя программа печатает Read 5 bytes. EOF: 1. Error: 0.
  11. Моя программа снова вызывает fread.
  12. fread вызовысистемный вызов read.
  13. Я снова нажимаю Ctrl + D.
  14. Системный вызов read возвращает 0.
  15. fread возвращает 0.
  16. Моя программа печатает Read zero bytes. EOF: 1. Error: 0. Exiting.

Почему это средство чтения стандартного ввода имеет такое поведение, в отличие от способа, которым любая другая программа, кажется, читает его?Это ошибка в patch?Как должен быть написан этот вид цикла, чтобы избежать такого поведения?

ОБНОВЛЕНИЕ: Это, похоже, связано с libc.Первоначально я испытал это на glibc 2.23-0ubuntu3 из Ubuntu 16.04.@Barmar отметил в комментариях, что это не происходит в macOS.Услышав это, я попытался скомпилировать ту же программу для musl 1.1.9-1, также из Ubuntu 16.04, и у нее не было этой проблемы.В musl последовательность событий удалена с 12 по 14, поэтому у нее нет проблемы, но в остальном она та же (за исключением несущественной детализации readv вместо read).

Теперь возникает вопрос: является ли glibc неправильным в своем поведении или патч неверен, если предположить, что его libc не будет иметь такого поведения?

1 Ответ

0 голосов
/ 06 октября 2018

Мне удалось подтвердить, что это связано с недвусмысленной ошибкой в ​​версиях glibc до 2.28 (commit 2cc7bad).Соответствующие кавычки из стандарта C :

байтовые функции ввода / вывода - те функции, описанные в этом подпункте, которые выполняют ввод / вывод: [...], fread

Функции байтового ввода читают символы из потока, как если бы они были последовательными вызовами функции fgetc.

Если указатель конца файла для потокаустанавливается, или , если поток находится в конце файла, для него устанавливается индикатор конца файла, а функция fgetc возвращает EOF.В противном случае функция fgetc возвращает следующий символ из входного потока, на который указывает stream.

(выделение "или" мой)

Следующая программа демонстрируетошибка с fgetc:

#include <stdio.h>

int main(void) {
    while(fgetc(stdin) != EOF) {
        puts("Read and discarded a character from stdin");
    }
    puts("fgetc(stdin) returned EOF");
    if(!feof(stdin)) {
        /* Included only for completeness. Doesn't occur in my testing. */
        puts("Standard violation! After fgetc returned EOF, the end-of-file indicator wasn't set");
        return 1;
    }
    if(fgetc(stdin) != EOF) {
        /* This happens with glibc in my testing. */
        puts("Standard violation! When fgetc was called with the end-of-file indicator set, it didn't return EOF");
        return 1;
    }
    /* This happens with musl in my testing. */
    puts("No standard violation detected");
    return 0;
}

Чтобы продемонстрировать ошибку:

  1. Скомпилируйте программу и выполните ее
  2. Нажмите Ctrl + D
  3. Нажмите Enter

Точная ошибка в том, что если индикатор потока конца файла установлен, но поток не находится в конце файла, fgetc из glibc вернет следующий символ изstream, а не EOF, как того требует стандарт.

Поскольку fread определяется в терминах fgetc, это причина того, что я изначально видел.Ранее сообщалось, что ошибка glibc # 1190 и исправлена ​​с момента коммита 2cc7bad в феврале 2018 года, который попал в glibc 2.28 в августе 2018.

...