3

The standard says that:

The perror() function shall not change the orientation of the standard error stream.

This is the implementation of perror() in GNU libc.

Following are the tests when stderr is wide-oriented, multibyte-oriented and not oriented, prior to calling perror(). Tests 1) and 2) are OK. The issue is in test 3).

1) stderr is wide-oriented:

#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 is multibyte-oriented:

#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 is not oriented:

#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

Why perror() changes orientation of stream if it is redirected? Is it proper behavior?

How does this code work? What is this __dup trick all about?

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Igor Liferenko
  • 1,499
  • 1
  • 13
  • 28
  • Did you consider checking the initial call to `fwide()` in the first sample? Was it successful? How do you know? – Jonathan Leffler Oct 10 '16 at 03:08
  • @RastaJedi It is said there about wide-character functions and multibyte functions, like `fprintf(stderr, "something");` vs. `fwprintf(stderr,L"something");`, but the example uses shell redirection, which is a different matter. – Igor Liferenko Oct 10 '16 at 03:10
  • @JonathanLeffler calling `fwide(stderr,0);` before calling `perror()` in example 3) returns `0`, as expected, because no operation has been performed on `stderr` yet. See edited example 3) - the tests show that everything is correct. – Igor Liferenko Oct 10 '16 at 03:12
  • @RastaJedi See edited example 3) - the tests show that everything is correct, so the problem is not in shell. – Igor Liferenko Oct 10 '16 at 03:17
  • I asked about the first example, not the third. However, the POSIX specification for [`fwide()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fwide.html) mentions that it is moderately hard to spot errors and suggests setting `errno = 0` before calling the function. However, it seems to me that the non-redirected variant of example 3 is the one with problems. The `perror()` writes to the `stderr` stream so it should have been given an orientation once `perror()` returns, but it appears not to have happened. The change when stderr is redirected is the behaviour I'd expect. – Jonathan Leffler Oct 10 '16 at 03:21
  • @JonathanLeffler The standard says that the opposite must be true, so it is the redirected case in example 3) which is wrong. See first link in OP. – Igor Liferenko Oct 10 '16 at 03:26
  • I observe that §7.22.2 **Streams** in ISO/IEC 9899:2011 (C11) says: _¶4 Each stream has an_ orientation. _After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a_ wide-oriented stream. _Similarly, once a byte input/output function has been applied to a stream without orientation, the stream becomes a_ byte-oriented stream. _[…continued…]_ – Jonathan Leffler Oct 10 '16 at 03:28
  • […continuation…] _Only a call to the `freopen` function or the `fwide` function can otherwise alter the orientation of a stream. (A successful call to `freopen` removes any orientation.) 5 Byte input/output functions shall not be applied to a wide-oriented stream and wide character input/output functions shall not be applied to a byte-oriented stream._ – Jonathan Leffler Oct 10 '16 at 03:30
  • @JonathanLeffler The above is true, but there is also this *The perror() function shall not change the orientation of the standard error stream.* And I think that they do not contradict each other. In non-redirected case of example 3) the behavior is correct. – Igor Liferenko Oct 10 '16 at 03:33
  • The comment from POSIX is curious — it is marked CX (extension compared to the C standard). But yes, under POSIX, it appears that `perror()` should not set the orientation of the stream — for reasons that are not explained. The C standard implies that `perror()` should set the orientation; POSIX seems to recognize that by marking its requirement as a change compared to the standard. So, yes, my previous comment about the expected behaviour of example 3 is wrong on POSIX but plausibly correct for non-POSIX. Since you're working on a POSIX-ish system, you have nominally got a bug — report it. – Jonathan Leffler Oct 10 '16 at 03:36
  • The `perror` spec says it doesn't **change** the orientation. I think that only applies if the orientation is already set, it's not talking about what happens if `stdout` is in its initial state without orientation. – Barmar Oct 10 '16 at 03:39
  • @Barmar that's exactly what I was going to just say :P. – RastaJedi Oct 10 '16 at 03:40
  • @Barmar Anyway, it's curious how this discrepancy in example 3) can be explained based on GNU libc implementation. – Igor Liferenko Oct 10 '16 at 03:41
  • @Barmar Even if there was not such requirement in POSIX, how `perror()` can change orientation? It's impossible without reopening the stream. BTW, is it possible to re-open `stderr` with another orientation? – Igor Liferenko Oct 10 '16 at 03:48
  • In example 3, it's not changing the orientation. The stream isn't oriented yet, so doing I/O on it sets the initial orientation, as required by ISO C. Since it was byte I/O rather than wide I/O, it becomes byte-oriented. – Barmar Oct 10 '16 at 03:52
  • @barmar: there is a clearer statement in Posix here: http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05 (fourth paragraph, last sentence.) – rici Oct 10 '16 at 04:13

2 Answers2

2

TL;DR: Yes, it's a bug in glibc. If you care about it, you should report it.

The quoted requirement that perror not change the stream orientation is in Posix, but does not seem to be required by the C standard itself. However, Posix seems quite insistent that the orientation of stderr not be changed by perror, even if stderr is not yet oriented. XSH 2.5 Standard I/O Streams:

The perror(), psiginfo(), and psignal() functions shall behave as described above for the byte output functions if the stream is already byte-oriented, and shall behave as described above for the wide-character output functions if the stream is already wide-oriented. If the stream has no orientation, they shall behave as described for the byte output functions except that they shall not change the orientation of the stream.

And glibc attempts to implement Posix semantics. Unfortunately, it doesn't quite get it right.

Of course, it is impossible to write to a stream without setting its orientation. So in an attempt to comply with this curious requirement, glibc attempts to make a new stream based on the same fd as stderr, using the code pointed to at the end of the OP:

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    { ...

which, stripping out the internal symbols, is essentially equivalent to:

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 extracts the fd from a standard C library stream. dup takes an fd, duplicates it, and returns the number of the copy. And fdopen creates a standard C library stream from an fd. In short, that doesn't reopen stderr; rather, it creates (or attempts to create) a copy of stderr which can be written to without affecting the orientation of stderr.

Unfortunately, it doesn't work reliably because of the mode:

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

That attempts to open a stream which allows both reading and writing. And it will work with the original stderr, which is just a copy of the console fd, originally opened for both reading and writing. But when you bind stderr to some other device with a redirect:

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

you are passing the executable an fd opened only for output. And fdopen won't let you get away with that:

The application shall ensure that the mode of the stream as expressed by the mode argument is allowed by the file access mode of the open file description to which fildes refers.

The glibc implementation of fdopen actually checks, and returns NULL with errno set to EINVAL if you specify a mode which requires access rights not available to the fd.

So you could get your test to pass if you redirect stderr for both reading and writing:

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

But what you probably wanted in the first place was to redirect stderr in append mode:

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

and as far as I know, bash does not provide a way to read/append redirect.

I don't know why the glibc code uses "w+" as a mode argument, since it has no intention of reading from stderr. "w" should work fine, although it probably won't preserve append mode, which might have unfortunate consequences.

rici
  • 234,347
  • 28
  • 237
  • 341
  • There is another possibility to leave stderr not oriented if it was not oriented prior to call of `perror()`. Instead of duplicating `stderr`, `perror()` can just reopen `stderr` if it was not oriented before the call to `perror()` (after outputting everything in default multibyte orientation). This will effectively make `stderr` not oriented. Can this approach be used safely? – Igor Liferenko Oct 10 '16 at 05:09
  • This is the link to the bug report: https://sourceware.org/bugzilla/show_bug.cgi?id=20677 BTW, do you know how to reopen a previously opened bug? I opened a bug here: https://sourceware.org/bugzilla/show_bug.cgi?id=20639 It was closed as UNCONFIRMED. But this is really a bug. I want to get it fixed. – Igor Liferenko Oct 10 '16 at 05:46
  • @igor: UNCONFIRMED doesn't mean that it is closed. It just means that no-one has confirmed that it is a bug. If you are going to copy an answer from stackoverflow, you really should provide a link to the discussion. – rici Oct 10 '16 at 06:06
  • provided link to the discussion – Igor Liferenko Oct 10 '16 at 06:13
  • see **EDIT** in OP – Igor Liferenko Oct 10 '16 at 06:22
  • @igor: perror only dups stderr if stderr is unoriented. If it has an orientation, perror outputs to it. Wrt fxprintf, I think that's probably a different question, although be aware that SO is not a forum for ranting about software you don't like. – rici Oct 10 '16 at 06:47
  • I asked a different question here http://stackoverflow.com/questions/39952983/how-multibyte-string-is-converted-to-wide-character-string-in-fxprintf-c-in-glibc – Igor Liferenko Oct 10 '16 at 09:18
  • The `w+` problem could probably be fixed by looking up the mode of the original `stderr` and copying that in `fdopen()`. But there's another bug in those whole approach of reopening, which would also occur if it simply used `write()`: the new stream doesn't share the original stderr's output buffer. – Barmar Oct 10 '16 at 14:09
  • @barmar: I don't believe the mode string is part of the FILE struct but it might be possible to deduce it. As for the buffer, it's true that the solution is imperfect, but since there cannot be anything in stderr's buffer (since stderr has no orientation) and the newly-created stream is immediately closed, flushing its buffer, I don't think the issue is serious. – rici Oct 10 '16 at 15:10
  • I wonder why this whole kludgey mechanism is needed. Can't `perror()` simply read the internal representation of the orientation, perform the output, and then set it back? It seems like it's trying to avoid crossing the abstraction boundary, but it's using internal functions to check the orientation. – Barmar Oct 10 '16 at 15:19
  • @Barmar: If you were going to bypass the kludge, you could just get the fd with `fileno()` and then `write` to that `fd`; problem solved. (As long as the stream has never been written to, that is; if the stream has been written to, it might have a buffer or it might be in a non-initial shift state, etc.). OTOH, as Florian Wiemer points out in a comment to [bug 20677](https://sourceware.org/bugzilla/show_bug.cgi?id=20677), there is no guarantee that `stderr` is backed by a Posix file descriptor, so `fileno` could fail. – rici Oct 10 '16 at 21:11
  • I do not understand what this "backed by a posix file descriptor" thing is about, and what can go wrong with it - please explain with an example – Igor Liferenko Oct 12 '16 at 05:43
  • @igor: A stream (or `FILE*`) is actually a kind of virtual I/O object; it knows how to buffer, how to keep and check error and eof flags, and a few other things. Most streams actually end up calling `write()` (or `read()`) with a Posix file descriptor. However, there are other possibilities. You can use the oddly named `fopencookie` or `fmemopen` to create `FILE*` objects which are not layered on top of Posix file descriptors, and once you've created one of those things, you could assign `stderr = myVirtualFileObject;`, in which case `fileno(stderr)` would return an error indication. – rici Oct 12 '16 at 05:51
  • @igor: the manpages for those two interfaces include examples, but since the examples call `perror` you probably won't want to use them with a `FILE*` you're planning to assign to `stderr`. :) (Forgot to mention that streams also understand orientation, which is not a feature of the underlying file or pseudo-file.) – rici Oct 12 '16 at 05:53
  • @Barmar if you care, there is new information on bug 20677 https://sourceware.org/bugzilla/show_bug.cgi?id=20677 – Igor Liferenko Oct 17 '16 at 08:26
1

I'm not sure if there's a good answer to "why" without asking the glibc developers - it may just be a bug - but the POSIX requirement seems to conflict with ISO C, which reads in 7.21.2, ¶4:

Each stream has an orientation. After a stream is associated with an external file, but before any operations are performed on it, the stream is without orientation. Once a wide character input/output function has been applied to a stream without orientation, the stream becomes a wide-oriented stream. Similarly, once a byte input/output function has been applied to a stream without orientation, the stream becomes a byte-oriented stream. Only a call to the freopen function or the fwide function can otherwise alter the orientation of a stream. (A successful call to freopen removes any orientation.)

Further, perror seems to qualify as a "byte I/O function" since it takes a char * and, per 7.21.10.4 ¶2, "writes a sequence of characters".

Since POSIX defers to ISO C in the event of a conflict, there is an argument to be made that the POSIX requirement here is void.

As for the actual examples in the question:

  1. Undefined behavior. A byte I/O function is called on a wide-oriented stream.
  2. Nothing at all controversial. The orientation was correct for calling perror and did not change as a result of the call.
  3. Calling perror oriented the stream to byte orientation. This seems to be required by ISO C but disallowed by POSIX.
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • The above is true, but there is also this: *The perror() function shall not change the orientation of the standard error stream.* And I think that they do not contradict each other. In non-redirected case of example 3) the behavior is correct. – Igor Liferenko Oct 10 '16 at 03:35
  • 1
    @IgorLiferenko: That text is from POSIX, not ISO C. My point is that it seems to conflict with the requirements of ISO C. – R.. GitHub STOP HELPING ICE Oct 10 '16 at 03:35
  • But in the GNU libc implementation they specifically mention this. So, there seems to be no conflict. And the example 3) confirms this. But only in part... – Igor Liferenko Oct 10 '16 at 03:36
  • How do they conflict? The ISO C spec you quoted is only talking about how the initial orientation is assigned to a stream. – Barmar Oct 10 '16 at 03:37
  • It says that only `freopen` and `fwide` can change the orientation. And the `perror` spec says it doesn't change the orientation, which is consistent with that. – Barmar Oct 10 '16 at 03:38
  • @Barmar: Added bold to the part you're missing. – R.. GitHub STOP HELPING ICE Oct 10 '16 at 03:43
  • I still don't see the conflict. Setting the initial orientation due to performing I/O, as the bolded part says, is not changing the orientation. – Barmar Oct 10 '16 at 03:47
  • Even if there was not such requirement in POSIX, how perror() can change orientation? It's impossible without reopening the stream. BTW, is it possible to re-open stderr with another orientation? – Igor Liferenko Oct 10 '16 at 03:51
  • @IgorLiferenko: Because all byte I/O functions (of which `perror` seems to be one) are required to set the orientation of the stream to byte-oriented (unless it was already wide-oriented, in which case calling them at all results in undefined behavior). – R.. GitHub STOP HELPING ICE Oct 10 '16 at 04:04
  • @igor: that's why the glibc perror function opens a copy of stderr. Note that the orientation is a feature of the stream, not the underlying fd. Unfortunately, there's a bug. – rici Oct 10 '16 at 04:05
  • @rici: "opening a copy" seems error-prone too. If it does that, how does it satisfy the locking requirements (ensuring that stderr output from other threads is not interleaved with the perror output)? – R.. GitHub STOP HELPING ICE Oct 10 '16 at 16:42
  • @R..: where does it say that perror is atomic? But anyway, my intent was only to try to explain the code. I didn't write it; I don't defend it; in fact, my answer calls it buggy. Furthermore, I doubt anyone who does maintain that code is reading this comment thread. I suggest that a more useful place to suggest bug fixes is precisely the [bug report opened by the OP](https://sourceware.org/bugzilla/show_bug.cgi?id=20677). – rici Oct 10 '16 at 17:27
  • @rici: Hidden in: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html "All functions that reference (FILE *) objects, except those with names ending in _unlocked, shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these (FILE *) objects." – R.. GitHub STOP HELPING ICE Oct 10 '16 at 18:18
  • @R.. So the fix would be to use flockfile to lock stderr while printing to the copy. If it doesn't already do that -- I didn't check, but I don't recall seeing it. But as I said, there is a useful forum to discuss these issues, and this is not it :-) – rici Oct 10 '16 at 19:40
  • @R..: OK, curiosity got the better of me. You're right, it doesn't lock stderr. On the other hand, `perror` doesn't "reference (a FILE*) object"; the description says that it "writes a sequence of characters to the standard error stream", but afaics it never specifies that it does so via the global `FILE*` object. (Yes, that is pure lawyerism, and I wrote it with my tongue firmly implanted in my cheek.) – rici Oct 10 '16 at 21:06
  • "Stream" means stdio FILE in the language of the standard. – R.. GitHub STOP HELPING ICE Oct 11 '16 at 03:17