2016-10-10 2 views
3

The standard говорит, что:Почему perror() изменяет ориентацию потока при его перенаправлении?

Функция PError() не изменяет ориентацию стандартного потока ошибок.

This является реализация perror() в GNU libc.

Ниже приведены тесты, когда stderr является широко ориентированным, многобайтовым и не ориентированным, до звонка perror(). Испытания 1) и 2) в порядке. Вопрос находится в тесте 3).

1) stderr широк-ориентированный:

#include <stdio.h> 
#include <wchar.h> 
#include <errno.h> 
int main(void) 
{ 
    fwide(stderr, 1); 
    errno = EINVAL; 
    perror(""); 
    int x = fwide(stderr, 0); 
    printf("fwide: %d\n",x); 
    return 0; 
} 
$ ./a.out 
Invalid argument 
fwide: 1 
$ ./a.out 2>/dev/null 
fwide: 1 

2) stderr является многобайтовым-ориентированным:

#include <stdio.h> 
#include <wchar.h> 
#include <errno.h> 
int main(void) 
{ 
    fwide(stderr, -1); 
    errno = EINVAL; 
    perror(""); 
    int x = fwide(stderr, 0); 
    printf("fwide: %d\n",x); 
    return 0; 
} 
$ ./a.out 
Invalid argument 
fwide: -1 
$ ./a.out 2>/dev/null 
fwide: -1 

3) stderr не ориентирован:

#include <stdio.h> 
#include <wchar.h> 
#include <errno.h> 
int main(void) 
{ 
    printf("initial fwide: %d\n", fwide(stderr, 0)); 
    errno = EINVAL; 
    perror(""); 
    int x = fwide(stderr, 0); 
    printf("fwide: %d\n", x); 
    return 0; 
} 
$ ./a.out 
initial fwide: 0 
Invalid argument 
fwide: 0 
$ ./a.out 2>/dev/null 
initial fwide: 0 
fwide: -1 

Почему perror() изменения ориентации потока, если он будет перенаправлен? Это правильное поведение?

Как работает this код работает? Что это такое __dup трюк все о?

+0

Вы рассматривали проверку начального вызова 'fwide()' в первом примере? Было ли это успешным? Откуда вы знаете? –

+0

@RastaJedi Здесь сказано о широких функциях символов и многобайтовых функциях, таких как 'fprintf (stderr,« something »);' vs. 'fwprintf (stderr, L« something »);', но в этом примере используется перенаправление оболочки, это другое дело. –

+0

@JonathanLeffler, вызывающий 'fwide (stderr, 0);' перед вызовом 'perror()' в примере 3) возвращает '0', как и ожидалось, поскольку пока не выполняется операция' stderr'. См. Отредактированный пример 3) - тесты показывают, что все правильно. –

ответ

2

TL; DR: Да, это ошибка в glibc. Если вас это волнует, вы должны сообщить об этом.

Процитированное требование о том, что perror не меняет ориентацию потока, находится в Posix, но, по-видимому, не требуется по стандарту C. Однако Posix кажется вполне настойчивым, что ориентация stderr не будет изменена на perror, даже если stderr еще не ориентирован. XSH 2.5 Standard I/O Streams:

Perror(), psiginfo(), и psignal() функции должны вести себя, как описано выше для выходных байтов функций, если поток уже байт-ориентированным, и будет вести себя, как описано выше для широкоэкранного символьные выходные функции, если поток уже широко ориентирован. Если поток не имеет ориентации, они должны вести себя так, как описано для выходных функций байта, за исключением того, что они не должны изменять ориентацию потока.

И glibc пытается реализовать семантику Posix. К сожалению, это не совсем правильно.

Конечно, невозможно записать в поток без установки его ориентации. Таким образом, в попытке соответствовать этому любопытному требованию, Glibc пытается создать новый поток, основанный на том же FD, как stderr, используя код указал в конце ОП:

58 if (__builtin_expect (_IO_fwide (stderr, 0) != 0, 1) 
59  || (fd = __fileno (stderr)) == -1 
60  || (fd = __dup (fd)) == -1 
61  || (fp = fdopen (fd, "w+")) == NULL) 
62 { ... 

который, зачистки из внутренние символы, по существу, эквивалентны:

if (fwide (stderr, 0) != 0 
    || (fd = fileno (stderr)) == -1 
    || (fd = dup (fd)) == -1 
    || (fp = fdopen (fd, "w+")) == NULL) 
    { 
    /* Either stderr has an orientation or the duplication failed, 
    * so just write to stderr 
    */ 
    if (fd != -1) close(fd); 
    perror_internal(stderr, s, errnum); 
    } 
else 
    { 
    /* Write the message to fp instead of stderr */ 
    perror_internal(fp, s, errnum); 
    fclose(fp); 
    } 

fileno извлекает ФД из стандартного потока библиотеки C. dup принимает fd, дублирует его и возвращает номер копии. И fdopen создает стандартный поток библиотеки C из fd. Короче говоря, это не возобновляет stderr; скорее, он создает (или пытается создать) копию stderr, которая может быть записана без влияния на ориентацию stderr.

К сожалению, это не работает надежно из-за режима работы:

fp = fdopen(fd, "w+"); 

, что попытки открыть поток, который позволяет одновременно читать и писать. И он будет работать с оригиналом stderr, который является просто копией консоли fd, первоначально открывшейся для чтения и записи. Но когда вы связываете STDERR к некоторому другому устройству с помощью редиректа:

$ ./a.out 2>/dev/null 

вы передаете исполняемый FD открыт только для вывода.И fdopen не позволит вам уйти с этим:

Приложение должно гарантировать, что режим потока, что выражается в режиме аргумента допускается режим доступа к файлу описания открытого файла, к которому относится Филдс ,

Реализация Glibc из fdopen фактически проверяет и возвращает NULL с errno набором для EINVAL если указан режим, который требует прав доступа, недоступных для ФД.

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

$ ./a.out 2<>/dev/null 

Но что вы, вероятно, хотели в первую очередь должно было перенаправить stderr в режиме добавления:

$ ./a.out 2>>/dev/null 

и, насколько я знаю, bash не предоставляет способ чтения/добавления перенаправления.

Я не знаю, почему код glibc использует "w+" как аргумент режима, так как он не намерен читать с stderr. "w" должен работать нормально, хотя, вероятно, он не сохранит режим добавления, что может привести к неудачным последствиям.

+0

Существует еще одна возможность оставить stderr не ориентированным, если он не был ориентирован до вызова 'perror()'. Вместо дублирования 'stderr'' 'perror()' может просто повторно открывать 'stderr', если он не был ориентирован перед вызовом' perror() '(после вывода всего в многобайтовой ориентации по умолчанию). Это фактически сделает 'stderr' не ориентированным. Можно ли безопасно использовать этот подход? –

+0

Это ссылка на отчет об ошибке: https://sourceware.org/bugzilla/show_bug.cgi?id=20677 Кстати, знаете ли вы, как открыть ранее открытую ошибку? Я открыл здесь ошибку: https://sourceware.org/bugzilla/show_bug.cgi?id=20639 Он был закрыт как UNCONFIRMED. Но это действительно ошибка. Я хочу исправить это. –

+0

@igor: UNCONFIRMED не означает, что он закрыт. Это просто означает, что никто не подтвердил, что это ошибка. Если вы собираетесь копировать ответ из stackoverflow, вы действительно должны предоставить ссылку на обсуждение. – rici

1

Я не уверен, если есть хороший ответ на вопрос «почему», не спрашивая разработчик GLibC - это может быть просто ошибка - но требование POSIX кажется, конфликтовать с ISO C, который читает в 7.21.2, ¶4:

Каждый поток имеет ориентацию. После того, как поток связан с внешним файлом, но перед выполнением каких-либо операций поток не имеет ориентации. После того, как широкая функция ввода/вывода символов была применена к потоку без ориентации, поток становится широко ориентированным потоком. Аналогично, как только функция ввода/вывода байта была применена к потоку без ориентации, поток становится потоком, ориентированным на байты. Только вызов функции freopen или функции fwide в противном случае может изменить ориентацию потока. (Успешный вызов freopen удаляет любую ориентацию.)

Далее, perror кажется квалифицировать как «функцию байт I/O», так как она занимает char * и, в 7.21.10.4 ¶2 «пишет последовательность символов».

Поскольку POSIX перебегает к ISO C в случае возникновения конфликта, возникает аргумент, что требование POSIX здесь недействительно.

Что касается реальных примеров в вопросе:

  1. Неопределенное поведение. Функция байтового ввода-вывода вызывается в широко ориентированном потоке. не
  2. Ничто вообще спорно. Ориентация была правильной для вызова perror и не изменилась в результате вызова.
  3. Вызов perror Ориентация потока на байтовую ориентацию. Это, по-видимому, требуется ISO C, но не разрешено POSIX.
+0

Вышеупомянутое верно, но там также: * Функция perror() не должна изменять ориентацию стандартного потока ошибок. * И я думаю, что они не противоречат друг другу. В случае без перенаправления примера 3) поведение правильное. –

+1

@IgorLiferenko: Этот текст из POSIX, а не ISO C. Я считаю, что он противоречит требованиям ISO C. –

+0

Но в реализации GNU libc они специально упоминают об этом. Так что, похоже, конфликта нет. И пример 3) подтверждает это. Но только частично ... –

Смежные вопросы