Home » Linux » What size should I allow for strerror_r?

What size should I allow for strerror_r?

Posted by: admin November 30, 2017 Leave a comment

Questions:

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);
Answers:

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

Questions:
Answers:

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).

Questions:
Answers:

This program (run online (as C++) here (btw, anybody knows how to compile pure C online?):

#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.