Thread overview
writing to closed stdout (II): exit status
Dec 15
kdevel
Dec 15
kdevel
Dec 15
kdevel
Dec 15
kdevel
Dec 16
Kagamin
6 days ago
kdevel
December 15

Sometimes the file descriptor 1 is closed when a program is executed (cf. [1]). Due to buffering [2] a write error is not detected until the buffers are flushed. But the flushing occurs after the return from main:

int main ()
{
   import std.stdio;
   writeln ("OK.");
   return 0;
}

(1>&- or >&- can be used to close stdout in BASH, likewise descriptor 1 can be closed in the D program using extern (C) int close (int); close (1);.)

$ dmd return0.d
$ ./return0 1>&-; echo $?
Failed to flush stdout: Bad file descriptor
1
$

Surprisingly when the return value is anything but 0 the exit code of the program becomes rv & 255. So in order to return 0 one can return 256:

int main ()
{
   import std.stdio;
   writeln ("OK.");
   return 256; // returns (256 & 0xff) == 0
}

Even more surprising is the fact that is behavior strongly resembles the Perl since v5.24.4 [3]

[1] Goodbye World! The perils of relying on output streams in C [PDF], p. 15 https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodbye-world.pdf
[2] What do fully buffered, line buffered and unbuffered mean in C? [closed] https://stackoverflow.com/questions/36573074/what-do-fully-buffered-line-buffered-and-unbuffered-mean-in-c
[3] https://stackoverflow.com/questions/50507849/weird-error-after-perl-upgrade-unable-to-flush-stdout/52106594#52106594

December 15

On Sunday, 15 December 2024 at 14:32:13 UTC, kdevel wrote:

>

Surprisingly when the return value is anything but 0 the exit code of the program becomes rv & 255.

The POSIX standard specifies that the status code you get from wait and waitpid is truncated to 8 bits. [1] Probably this is because it needs to be packed into a single int variable alongside other metadata (WIFEXITED, WIFSIGNALED, and so on), and the C standard only guarantees that int will have at least 16 bits. [2]

It's possible to get the full exit status from waitid [3], so you can't necessarily assume that the upper bits are ignored, but relying on them probably isn't a great idea either.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
[2] http://port70.net/~nsz/c/c11/n1570.html#5.2.4.2.1
[3] https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html

December 15

On Sunday, 15 December 2024 at 17:16:11 UTC, Paul Backus wrote:

>

On Sunday, 15 December 2024 at 14:32:13 UTC, kdevel wrote:

>

Surprisingly when the return value is anything but 0 the exit code of the program becomes rv & 255.

The POSIX standard specifies that the status code you get from wait and waitpid is truncated to 8 bits.

Sure. My wording is somewhat misleading. What I find surprising is that if 256 is returned the exit status of the program becomes 0 whereas it becomes 1 in the case of a returned 0:

$ cat return0.d
int main ()
{
   import std.stdio;
   writeln ("OK.");
   return 0;
}
$ dmd -run return0 1>&-; echo $?
Failed to flush stdout: Bad file descriptor
1
$ cat return256.d
int main ()
{
   import std.stdio;
   writeln ("OK.");
   return 256;
}
$ dmd -run return256 1>&-; echo $?
Failed to flush stdout: Bad file descriptor
0
December 15

On Sunday, 15 December 2024 at 18:46:26 UTC, kdevel wrote:

>

On Sunday, 15 December 2024 at 17:16:11 UTC, Paul Backus wrote:

>

On Sunday, 15 December 2024 at 14:32:13 UTC, kdevel wrote:

>

Surprisingly when the return value is anything but 0 the exit code of the program becomes rv & 255.

The POSIX standard specifies that the status code you get from wait and waitpid is truncated to 8 bits.

Sure. My wording is somewhat misleading. What I find surprising is that if 256 is returned the exit status of the program becomes 0 whereas it becomes 1 in the case of a returned 0:

I did some more digging and it turns out that this is a feature of the GNU C runtime (i.e., the code that calls main):

>

While nominally the return value is of type int, in fact the exit status gets truncated to eight bits; if main returns the value 256, the exit status is 0.

https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Values-from-main.html

December 15

On Sunday, 15 December 2024 at 17:16:11 UTC, Paul Backus wrote:

>

It's possible to get the full exit status from waitid [3],

Actually not on my linux machine:

$ cat status.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main ()
{
   int rc = fork ();
   assert (rc != -1);
   if (rc == 0) /* child */
      _exit (-1);
   else {
      siginfo_t si;
      waitid (P_ALL, 0, &si, WEXITED);
      printf ("%x\n", si.si_status);
   }
   return 0;
}
$ cc status.c
$ ./a.out
ff

But your reference [3] does not say that it should. Also [4] only says that the lower 8 bit constitute the the corresponding part of si_status. Not more not less. Likewise the code on SO [5] does not yield more than 8 bits here on my linux box.

[4] https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_13

[5] https://stackoverflow.com/questions/179565/exit-codes-bigger-than-255-possible

December 15

On Sunday, 15 December 2024 at 19:02:33 UTC, Paul Backus wrote:

> > >

[...]
Sure. My wording is somewhat misleading. What I find surprising is that if 256 is returned the exit status of the program becomes 0 whereas it becomes 1 in the case of a returned 0:

I did some more digging and it turns out that this is a feature of the GNU C runtime (i.e., the code that calls main):

Sorry for replying again. The truncation to eight bit is not my point. My point is that in the case of a closed stdout

   return 0;

causes an exit status of 1 but

   return 256;

lets exit status become 0.

December 15

On Sunday, 15 December 2024 at 19:53:34 UTC, kdevel wrote:

>

Sorry for replying again. The truncation to eight bit is not my point. My point is that in the case of a closed stdout

   return 0;

causes an exit status of 1 but

   return 256;

lets exit status become 0.

Oh, yeah, that happens because druntime specifically checks for exit status 0:

https://github.com/dlang/dmd/blob/v2.109.1/druntime/src/rt/dmain2.d#L535-L543

I guess the correct thing to do here would be to check if result & 0xFF == 0.

December 16

On Sunday, 15 December 2024 at 19:41:00 UTC, kdevel wrote:

>

On Sunday, 15 December 2024 at 17:16:11 UTC, Paul Backus wrote:

>

It's possible to get the full exit status from waitid [3],

Actually not on my linux machine:

Try ssi_status from signalfd.

6 days ago

On Monday, 16 December 2024 at 09:33:12 UTC, Kagamin wrote:

>

On Sunday, 15 December 2024 at 19:41:00 UTC, kdevel wrote:

>

On Sunday, 15 December 2024 at 17:16:11 UTC, Paul Backus wrote:

>

It's possible to get the full exit status from waitid [3],

Actually not on my linux machine:

Try ssi_status from signalfd.

Like this?

#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/signalfd.h>

int main ()
{
   int rc = fork ();
   assert (rc != -1);

   if (rc == 0) { /* child */
      sleep (1);
      _exit (-1);
   }
   else {
      sigset_t mask;
      sigemptyset(&mask);
      sigaddset(&mask, SIGCHLD);
      int sfd = signalfd (-1, &mask, 0);
      assert (sfd != -1);

      rc = sigprocmask (SIG_BLOCK, &mask, 0);
      assert (rc != -1);

      struct signalfd_siginfo sfds;
      ssize_t s = read (sfd, &sfds, sizeof sfds);
      assert (s == sizeof sfds);

      printf ("%x\n", sfds.ssi_status);
   }
   return 0;
}