Thread overview
writing to closed stdout (II): exit status
Dec 15, 2024
kdevel
Dec 15, 2024
Paul Backus
Dec 15, 2024
kdevel
Dec 15, 2024
Paul Backus
Dec 15, 2024
kdevel
Dec 15, 2024
Paul Backus
Dec 15, 2024
kdevel
Dec 16, 2024
Kagamin
Dec 17, 2024
kdevel
December 15, 2024

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, 2024

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, 2024

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, 2024

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, 2024

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, 2024

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, 2024

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, 2024

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.

December 17, 2024

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;
}