On Thu, 27 Jan 2022 22:08:57 +0000
Reuben Thomas <r...@sc3d.org> wrote:

> On Wed, 26 Jan 2022 at 12:59, Sergei Trofimovich <sly...@gmail.com> wrote:
> >  
> 
> Thanks for the report!
> 
> > From what I understand at
> >     
> > https://wiki.musl-libc.org/functional-differences-from-glibc.html#Character-sets-and-locale
> > musl deliberately supports any char -> wchar_t (and back) conversion for
> > any char in LC_CTYPE=C. And thus the multibyte-1 test will always fail
> > there.  
> 
> I also found this problem on macOS. I tried on my GNU/Linux system
> using an ISO-8859 locale for the test, but the multibyte conversion
> does not fail! (e.g. in en_GB.iso88591). I also tried with
> ru_RU.KOI8-R, same result.
> 
> It seems then that it's not going to be possible in general to find a
> locale that will fail. These results suggest that on some libc's,
> conversion will always succeed.
> 
> Hence, I'm removing the test.
> 
> > Not sure why atexit-1 fails. It seems to rely on
> > close_stdout_set_file_name() call in atexit(), but I don't see where in
> > code it's registered to call.  
> 
> Line 155 registers close_stdout. I can't see anything wrong with the
> way this is done; I'd be very grateful if you could investigate
> further.

Aha, I misinterpreted the atexit handler and did not notice
close_stdout_set_file_name() is part of gnulib.

Attached possible patch. Passes atexit-1 test on musl and glibc.

Some details:

The real reason to failure on musl is stream status and errno value
status in close_stream(). If I add a bit of fprintf() debugging it
becomes clearer:

  void
  close_stdout (void)
  {
    fprintf(stderr, "%s: errno=%d\n", __func__, errno);
    if (close_stream (stdout) != 0
        && !(ignore_EPIPE && errno == EPIPE))
      {
        fprintf(stderr, "%s: after close_stream() errno=%d\n", __func__, errno);
  ...


Execution on glibc:

  close_stdout: errno=0
  close_stdout: after close_stream() errno=28
  ./hello: write error: No space left on device


Execution on glibc:

  close_stdout: errno=28
  close_stdout: after close_stream() errno=0
  hello: write error

On glibc stdout flush did not happen yet by the time we get to atexit().
ltrace also gives the same hint:

On glibc:

$ ltrace ./hello
...
strlen("Hello, world!")                                     = 13
malloc(56)                                                  = 0x10c78c0
mbsrtowcs(0x10c78c0, 0x7ffeb2b630a0, 14, 0x7ffeb2b630a8)    = 13
wprintf(0x408298, 0x10c78c0, 177, 0)                        = 14
free(0x10c78c0)                                             = <void>
exit(0 <unfinished ...>
__errno_location()                                          = 0x7fa894ceb548
fprintf(0x7fa894cdf440, "%s: errno=%d\n", "close_stdout", 0close_stdout: errno=0
) = 22
__fpending(0x7fa894cdf520, 0x7ffeb2b609d0, 0, 0x7fa894c11343) = 14
ferror(0x7fa894cdf520)                                      = 0
fclose(0x7fa894cdf520)                                      = -1
fprintf(0x7fa894cdf440, "%s: after close_stream() errno=%"..., "close_stdout", 
28close_stdout: after close_stream() errno=28
) = 44
dcgettext(0, 0x40836e, 5, 0x7fa894c11343)                   = 0x40836e
error(0, 28, 0x4080c8, 0x40836e./hello: write error: No space left on device
)                            = 0
_exit(1 <no return ...>
+++ exited (status 1) +++

Note that wprintf() queued 14 symbols successfully.

On musl queueing did not happen and thus stream close did not return error:

$ ltrace ./hello
...
strlen("Hello, world!")                                     = 13
malloc(56)                                                  = 0x5570a96ddb10
mbsrtowcs(0x5570a96ddb10, 0x7ffdcb755398, 14, 0x7ffdcb7553a0) = 13
wprintf(0x5570a96d9298, 0x5570a96ddb10, 0xffffff3e, 1)      = 0xffffffff
free(0x5570a96ddb10)                                        = <void>
exit(0 <unfinished ...>
__errno_location()                                          = 0x7fad63ed0b1c
fprintf(0x7fad63ece0c0, "%s: errno=%d\n", "close_stdout", 28close_stdout: 
errno=28
) = 23
__fpending(0x7fad63ece2c0, 0x7ffdcb755040, 0xffffffff, 0)   = 0
ferror(0x7fad63ece2c0)                                      = 1
fclose(0x7fad63ece2c0)                                      = 0
__errno_location()                                          = 0x7fad63ed0b1c
fprintf(0x7fad63ece0c0, "%s: after close_stream() errno=%"..., "close_stdout", 
0close_stdout: after close_stream() errno=0
) = 43
gettext(0x5570a96d92ba, 0x7ffdcb755040, 0xffffffff, 0)      = 0x5570a96d92ba
fileno(0x7fad63ece2c0)                                      = 1
fcntl(1, 3, 0x636f6c2f7273752f, 0x5570a96d92ba)             = -1
fprintf(0x7fad63ece0c0, "%s: ", "hello"hello: )                    = 7
vfprintf(0x7fad63ece0c0, "%s", 0x7ffdcb755230write error)              = 11
putc(10, 0x7fad63ece0c0, 0xffffffff, 0
)                     = 10
fflush(0x7fad63ece0c0)                                      = 0
_exit(1 <no return ...>
+++ exited (status 1) +++

On glibc write() happens in atexit:

$ strace ./hello
...
newfstatat(1, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x7), ...}, 
AT_EMPTY_PATH) = 0
ioctl(1, TCGETS, 0x7ffee70babb0)        = -1 ENOTTY (Inappropriate ioctl for 
device)
write(2, "close_stdout: errno=0\n", 22close_stdout: errno=0
) = 22
write(1, "Hello, world!\n", 14)         = -1 ENOSPC (No space left on device)
close(1)                                = 0
write(2, "close_stdout: after close_stream"..., 44close_stdout: after 
close_stream() errno=28
) = 44
write(2, "./hello: ", 9./hello: )                = 9
write(2, "write error", 11write error)             = 11

On musl write() happens way before atexit, at main():

$ strace ./hello
...
writev(1, [{iov_base="Hello, world!", iov_len=13}, {iov_base="\n", iov_len=1}], 
2) = -1 ENOSPC (No space left on device)
writev(2, [{iov_base="close_stdout: errno=28\n", iov_len=23}, {iov_base=NULL, 
iov_len=0}], 2close_stdout: errno=28
) = 23
close(1)                                = 0
writev(2, [{iov_base="close_stdout: after close_stream"..., iov_len=43}, 
{iov_base=NULL, iov_len=0}], 2close_stdout: after close_stream() errno=0
) = 43
fcntl(1, F_GETFL)                       = -1 EBADF (Bad file descriptor)
writev(2, [{iov_base="hello: ", iov_len=7}, {iov_base=NULL, iov_len=0}], 
2hello: ) = 7
writev(2, [{iov_base="write error", iov_len=11}, {iov_base=NULL, iov_len=0}], 
2write error) = 11
writev(2, [{iov_base="", iov_len=0}, {iov_base="\n", iov_len=1}], 2
) = 1
exit_group(1)                           = ?
+++ exited with 1 +++
...

-- 

  Sergei
From d1828b32e00bbd6fe3dee3bdfafb6adf62a9dabc Mon Sep 17 00:00:00 2001
From: Sergei Trofimovich <sly...@gmail.com>
Date: Sat, 29 Jan 2022 10:11:19 +0000
Subject: [PATCH] hello: force stdout buffering until atexit()

Before the change atexit-1 test failed on musl libc.
test assumed that actual I/O to stdout will happen at
atexit() call and not before. But musl performed I/O
earlier and did not buffer any data.

To maintain the assumption of buffered I/O the change
enables full buffering for stdout with enough buffer
to hold all potential output.

* main: set full buffering on stdout.
---
 src/hello.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/hello.c b/src/hello.c
index 2e7d38e..715a3bb 100644
--- a/src/hello.c
+++ b/src/hello.c
@@ -134,6 +134,9 @@ main (int argc, char *argv[])
   mbstate_t mbstate = { 0, };
   size_t len;
 
+  /* Make sure real I/O will not happen until atexit().  */
+  setvbuf(stdout, NULL, _IOFBF, 1024);
+
   set_program_name (argv[0]);
 
   /* Set locale via LC_ALL.  */
-- 
2.34.1

Reply via email to