36

The OpenGroup POSIX.1-2001 defines strerror_r, as does The Linux Standard Base Core Specification 3.1. But I can find no reference to the maximum size that could be reasonably expected for an error message. I expected some define somewhere that I could put in my code but there is none that I can find.

The code must be thread safe. Which is why strerror_r is used and not strerror.

Does any one know the symbol I can use? I should I create my own?


Example

int result = gethostname(p_buffy, size_buffy);
int errsv = errno;
if (result < 0)
{
    char buf[256];
    char const * str = strerror_r(errsv, buf, 256);
    syslog(LOG_ERR,
             "gethostname failed; errno=%d(%s), buf='%s'",
             errsv,
             str,
             p_buffy);
     return errsv;
}

From the documents:

The Open Group Base Specifications Issue 6:

ERRORS

The strerror_r() function may fail if:

  • [ERANGE] Insufficient storage was supplied via strerrbuf and buflen to contain the generated message string.

From the source:

glibc-2.7/glibc-2.7/string/strerror.c:41:

    char *
    strerror (errnum)
         int errnum;
    {
        ...
        buf = malloc (1024);
mat_geek
  • 2,481
  • 3
  • 24
  • 29
  • 2
    Note that instead using [`strerror`](http://man7.org/linux/man-pages/man3/strerror.3.html) function, you can use [`syslog`](http://man7.org/linux/man-pages/man3/syslog.3.html) with `%m` specifier (which is [POSIX-compliant](http://stackoverflow.com/questions/1780599/i-never-really-understood-what-is-posix)). Example: `syslog(LOG_ERR, "Error occured, details: %m")`. Read [`syslog`](http://man7.org/linux/man-pages/man3/syslog.3.html#DESCRIPTION) manual to learn more. Unfortunately I don't know if `%m` is thread safe as [`strerror_r`](http://man7.org/linux/man-pages/man3/strerror.3.html) is. – patryk.beza Sep 18 '16 at 13:23

4 Answers4

13

Having a sufficiently large static limit is probably good enough for all situations. If you really need to get the entire error message, you can use the GNU version of strerror_r, or you can use the standard version and poll it with successively larger buffers until you get what you need. For example, you may use something like the code below.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Call strerror_r and get the full error message. Allocate memory for the
 * entire string with malloc. Return string. Caller must free string.
 * If malloc fails, return NULL.
 */
char *all_strerror(int n)
{
    char *s;
    size_t size;

    size = 1024;
    s = malloc(size);
    if (s == NULL)
        return NULL;

    while (strerror_r(n, s, size) == -1 && errno == ERANGE) {
        size *= 2;
        s = realloc(s, size);
        if (s == NULL)
            return NULL;
    }

    return s;
}

int main(int argc, char **argv)
{
    for (int i = 1; i < argc; ++i) {
        int n = atoi(argv[i]);
        char *s = all_strerror(n);
        printf("[%d]: %s\n", n, s);
        free(s);
    }

    return 0;
}
  • For GNU `strerror_r`, from the link you gave: (the string may be truncated if buflen is too small). How does this solve the problem? – Michael Mior Aug 27 '10 at 00:56
  • @Michael, the XSI-compliant strerror_r returns an error if the buffer is too small. The GNU one is less useful, it seems to me. –  Aug 28 '10 at 23:10
  • 1
    increasing the buffer size, 2 bytes at a time, doesn't really impress me. try adding 256 bytes (1/4 the original) or doubling it each time.. then you'd have an algorithm. – Thomas W Jun 01 '12 at 00:51
  • though the desirability & reliability of using this approach at all, are dubious. probably just defining a suitably-large fixed buffer wins -- on simplicity & reliability grounds. – Thomas W Jun 01 '12 at 00:52
  • Thomas, oh, I've mistakenly used `+=` instead of `*=`; fixing. –  Jun 01 '12 at 04:52
  • This works fine, thank you. It's a bit ridiculous that we even need to worry about it though. Whoever defined the POSIX `strerror_r` interface put even less thought into it than the original `strerror`. This is exemplified bad design, no encapsulation at all. – Daniel Saner Jan 18 '13 at 15:56
  • Also make sure to use a temporary variable for that realloc. Otherwise it might fail resulting in losing your original pointer. – Steven Feb 16 '17 at 11:06
11

I wouldn't worry about it - a buffer size of 256 is far more than sufficient, and 1024 is overkill. You could use strerror() instead of strerror_r(), and then optionally strdup() the result if you need to store the error string. This isn't thread-safe, though. If you really need to use strerror_r() instead of strerror() for thread safety, just use a size of 256. In glibc-2.7, the longest error message string is 50 characters ("Invalid or incomplete multibyte or wide character"). I wouldn't expect future error messages to be significantly longer (in the worst case, a few bytes longer).

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • And if you must use a symbol, I'd suggest BUFSIZ from – Jonathan Leffler Jan 08 '09 at 06:07
  • 7
    Just to clarify, I "really need to use strerror_r() instead of strerror() for thread safety". – mat_geek Jan 08 '09 at 06:52
  • 16
    Note that messages could easily be 3 times longer in a non-English locale just because the characters they use are above `U+0800`. For ideographic languages this is hardly an issue because while characters are 3-bytes instead of 1-byte each, words are often 1-2 characters instead of 6-12 characters. But in other non-Latin-alphabet languages (especially Indic scripts) I could see error messages easily reaching 256 bytes. – R.. GitHub STOP HELPING ICE Feb 05 '11 at 19:23
7

This program (run online (as C++) here):

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(){
        const int limit = 5;
        int unknowns = 0;
        int maxlen = 0;
        int i=0; char* s = strerror(i);
        while(1){
            if (maxlen<strlen(s)) maxlen = strlen(s);
            if (/*BEGINS WITH "Unknown "*/ 0==strncmp("Unknown ", s , sizeof("Unknown ")-1) )
                unknowns++;
            printf("%.3d\t%s\n", i, s);
            i++; s=strerror(i);
            if ( limit == unknowns ) break;
        }
        printf("Max: %d\n", maxlen);
        return 0;
}

lists and prints all the errors on the system and keeps track of the maximum length. By the looks of it, the length does not exceed 49 characters (pure strlen's without the final \0) so with some leeway, 64–100 should be more than enough.

I got curious if the whole buffer size negotiation couldn't simply be avoided by returning structs and whether there was a fundamental reason for not returning structs. So I benchmarked:

#define _POSIX_C_SOURCE 200112L //or else the GNU version of strerror_r gets used
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

typedef struct { char data[64]; } error_str_t;
error_str_t strerror_reent(int errn) __attribute__((const));
error_str_t strerror_reent(int errn){
    error_str_t ret;
    strerror_r(errn, ret.data, sizeof(ret));
    return ret;
}


int main(int argc, char** argv){
    int reps = atoi(argv[1]);
    char buf[64];
    volatile int errn = 1;
    for(int i=0; i<reps; i++){
#ifdef VAL
        error_str_t err = strerror_reent(errn);
#else
        strerror_r(errn, buf, 64);
#endif
    }
    return 0;
}

and the performance difference between the two at -O2 is minimal:

gcc -O2 : The VAL version is slower by about 5%
g++ -O2 -x c++ : The VAL version is faster by about 1% than the standard version compiled as C++ and by about 4% faster than the standard version compiled as C (surprisingly, even the slower C++ version beats the faster C version by about 3%).

In any case, I think it's extremely weird that strerror is even allowed to be thread unsafe. Those returned strings should be pointers to string literals. (Please enlighten me, but I can't think of a case where they should be synthesized at runtime). And string literals are by definition read only and access to read only data is always thread safe.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • There is some discussion of the reasons for why `strerror` may not be thread safe in the comments to [this answer](http://stackoverflow.com/a/900363). – Carsten S Nov 28 '16 at 13:26
  • @CarstenS Thanks. I still think locales are a poor reason for the non-reentrancy. Each locale could simply have its own set of static readonly strings. I guess somebody might want to synthesize the localized results out of common subpieces but doing this sort of deduplication would seem to me like an extreme case of premature optimization, and this is coming from a premature optimizer. Unfortunately there's not much of a point in arguing with standards. – Petr Skocik Nov 28 '16 at 14:47
  • 1
    I also find it annoying. I think the problem that the comments to the answer to that other question hinted at is that the locale might be deinstalled while someone still has a pointer to an error string. Anyway, not my decisions either ;) – Carsten S Nov 28 '16 at 17:49
  • 1
    @PSkocik - Regarding running C online, you might try [TIO](https://tio.run/#c-clang) or [ideone](https://ideone.com). – owacoder Jul 06 '19 at 01:48
0

Nobody has provided a definitive answer yet, so I looked into this further and there's a better function for the job, perror(3), as you will probably want to display this error somewhere, which is what I'd recommend you use unless your requirements really require you not to.

That's not a full answer, but the reason to use it is because it uses proper size buffer suitable for any locale. It internally uses strerror_r(3), these two functions conform to POSIX standard and are widely available, therefore in my eyes they're authoritative source of truth in this matter.

excerpt from glibc implementation:

static void
perror_internal (FILE *fp, const char *s, int errnum)
{
  char buf[1024];
  const char *colon;
  const char *errstring;
  if (s == NULL || *s == '\0')
    s = colon = "";
  else
    colon = ": ";
  errstring = __strerror_r (errnum, buf, sizeof buf);
  (void) __fxprintf (fp, "%s%s%s\n", s, colon, errstring);
}

From this I can infer, that at this moment in time, and given stability of such things, in forseeable future, you will never go wrong with a buffer size of 1024 chars.