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"
должен работать нормально, хотя, вероятно, он не сохранит режим добавления, что может привести к неудачным последствиям.
Вы рассматривали проверку начального вызова 'fwide()' в первом примере? Было ли это успешным? Откуда вы знаете? –
@RastaJedi Здесь сказано о широких функциях символов и многобайтовых функциях, таких как 'fprintf (stderr,« something »);' vs. 'fwprintf (stderr, L« something »);', но в этом примере используется перенаправление оболочки, это другое дело. –
@JonathanLeffler, вызывающий 'fwide (stderr, 0);' перед вызовом 'perror()' в примере 3) возвращает '0', как и ожидалось, поскольку пока не выполняется операция' stderr'. См. Отредактированный пример 3) - тесты показывают, что все правильно. –