Hi,

what should printf(3) return on %e/%f/%g/%a malloc(3) failure?

Neither POSIX nor our manual page seem fully conclusive.

POSIX says:

  The fprintf() and printf() functions may fail if:
  [ENOMEM] Insufficient storage space is available.

  RETURN VALUE
  Upon successful completion, the fprintf() and printf() functions
  shall return the number of bytes transmitted.

  If an output error was encountered, these functions shall return
  a negative value and set errno to indicate the error.

It is not obvious to me whether malloc(3) failure is an "output error".
If not, then the return value might be unspecified for that case.

Our manual page agrees with almost the same wording, so it doesn't
help either.


The current behaviour of our implementation is to return the number
of characters printed *and* set errno = ENOMEM.  In various cases,
that yields really weird results.  For example,

  printf("test %.5000000f", 1.0);

sets ENOMEM and returns 5 but does not actually print anything
because the PRINT() macro only adds "test " to the internal iov[]
data structure and the FLUSH() macro does not get called before
the %f bails out of the function.

Even weirder,

  ret = asprintf(&cp, "%s%.5000000f", argv[1], 1.0);

is equivalent, in our implementation, to

  ret = strlen(argv[1]);
  cp = strdup(argv[1]);
  errno = ENOMEM;

so a buffer does get allocated and returned, but its content is
incomplete.

To use our implementation correctly, the following idiom would be
required:

  char  *s;
  double x;
  size_t minsz;
  int    ret;

  minsz = strlen(s) + 2;
  ret = asprintf(&cp, "%s%f", s, x);
  if (ret < 0 || ret < (int)minsz)
        err(1, NULL);

Nobody does that.  Note in particular that the "ret < 0" is
required because minsz may be too large to be represented
as an integer, and it is sufficient to guard the (int) cast
because in that case, printf(3) returns -1/EOVERFLOW.  Also
note that the calculation of minsz can become arbitrarily
complicated for more complicated format strings, to the point
of being almost impossible.  For example, for "%.1f%.1f", a
return value of 6 may mean that both arguments were 1.0,
or it may mean that the first one was 1.2345 and then memory
was exhausted.

Alternatively, you could do the "save errno, set errno = 0,
call printf, inspect errno, restore errno" dance, but nobody
does that either, and it would be insane.


As related data points, for EOVERFLOW, we do always return -1,
and for EILSEQ, we changed the code some time ago to return -1 -
even though in both of these cases, it is not completely obvious
whether those should be considered "output errors" in the POSIX
sense.

For ENOMEM, both glibc and Solaris 11 return -1 according to my
testing, and NetBSD does the same according to code inspection.  In
FreeBSD, my impression is that dtoa() uses malloc(3), too, but i
failed to find any error handling code, so i guess they chose to
simply segfault - not sure, though.


In summary, i think we ought to return -1.

It's the only option that allows a sane usage pattern (and in
particular the one that people *are* actually using, if they check
for errors at all), POSIX at least doesn't forbid it, and most
others seem to do it, too.

What do you think?
  Ingo


Index: stdio/vfprintf.c
===================================================================
RCS file: /cvs/src/lib/libc/stdio/vfprintf.c,v
retrieving revision 1.77
diff -u -p -r1.77 vfprintf.c
--- stdio/vfprintf.c    29 Aug 2016 12:20:57 -0000      1.77
+++ stdio/vfprintf.c    26 Jul 2017 07:29:33 -0000
@@ -701,6 +701,7 @@ reswitch:   switch (ch) {
                                    &expt, &signflag, &dtoaend);
                                if (dtoaresult == NULL) {
                                        errno = ENOMEM;
+                                       ret = -1;
                                        goto error;
                                }
                        } else {
@@ -710,6 +711,7 @@ reswitch:   switch (ch) {
                                    &expt, &signflag, &dtoaend);
                                if (dtoaresult == NULL) {
                                        errno = ENOMEM;
+                                       ret = -1;
                                        goto error;
                                }
                        }
@@ -747,6 +749,7 @@ fp_begin:
                                    &expt, &signflag, &dtoaend);
                                if (dtoaresult == NULL) {
                                        errno = ENOMEM;
+                                       ret = -1;
                                        goto error;
                                }
                        } else {
@@ -756,6 +759,7 @@ fp_begin:
                                    &expt, &signflag, &dtoaend);
                                if (dtoaresult == NULL) {
                                        errno = ENOMEM;
+                                       ret = -1;
                                        goto error;
                                }
                                if (expt == 9999)

Reply via email to