How to use strerror_l with current locale?
Asked Answered
I

2

10

I'm fixing some Linux code which used strerror (not thread-safe) for multi-threading. I found that strerror_r and strerror_l are both thread-safe. Due to different definitions for strerror_r (depending on _GNU_SOURCE it is differently defined) I'd like to use the newer strerror_l function, but how am I supposed to obtain a locale_t object for the current locale? I'm not using iconv or anything, just plain libc, and I don't see how I can obtain a "default locale" object (I don't care in what language the error is printed, I just want a human readable string.)

Infantine answered 9/1, 2015 at 10:26 Comment(0)
E
4

If you pass "" to the locale parameter newlocale will allocate a locale object set to the current native locale[1]

[1]http://pubs.opengroup.org/onlinepubs/9699919799/functions/newlocale.html

static  locale_t locale;

bool MyStrerrorInit(void)
{
    locale = newlocale(LC_ALL_MASK,"",(locale_t)0);
    
    if (locale == (locale_t)0) {
       return false;
    }
 
    return true;
}
    
char * MyStrerror(int error)
{
    return strerror_l(error, locale);
}
Engage answered 15/1, 2015 at 15:29 Comment(1)
You can use LC_ALL_MASK instead of ORing all of the category masksCorridor
S
5

You could use POSIX uselocale:

strerror_l(errno, uselocale((locate_t)0));

@TavianBarnes pointed out in the comment that this code can exhibit undefined behavior:

[CX] [Option Start] The behavior is undefined if the locale argument to strerror_l() is the special locale object LC_GLOBAL_LOCALE or is not a valid locale object handle. [Option End]

https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html

Upon successful completion, the uselocale() function shall return a handle for the thread-local locale that was in use as the current locale for the calling thread on entry to the function, or LC_GLOBAL_LOCALE if no thread-local locale was in use.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/uselocale.html

Use of LC_GLOBAL_LOCALE with *_l() functions clarified the UB behavior of *_l() functions when given LC_GLOBAL_LOCALE as the locale, provided a version without undefined behavior and discussed alternatives.

An obvious version that isn't thread safe:

locale_t locale = uselocale((locate_t)0);

if (locale == LC_GLOBAL_LOCALE) {
  strerror(errno);
} else {
  strerror_l(errno, locale);
}

A thread-safe version:

locale_t locale = uselocale((locate_t)0);
locale_t copy = loc;

if (copy == LC_GLOBAL_LOCALE) {
  copy = duplocale(copy);
}

strerror_l(errno, copy);

if (loc == LC_GLOBAL_LOCALE) {
  freelocale(copy);
}

Here's a full working example from @TavianBarnes.

Stung answered 18/9, 2021 at 13:47 Comment(7)
Well, technically, NULL is odd here. It's not NULL, it's (locale_t)0. uselocale((locale_t)0). Is locale_t guaranteed to be a pointer?Ockham
@Ockham You're right, I mixed uselocale up with setlocale. I don't think locale_t is guaranteed to be a pointer, except that it is in glibc. I've incorporated this into my answer. Thank you.Stung
I upvoted this answer but then noticed a problem: uselocale() shall return ... LC_GLOBAL_LOCALE if no thread-local locale was in use, but the behavior is undefined if the locale argument to strerror_l() is the special locale object LC_GLOBAL_LOCALE.Crown
TIL about this UB. Thanks @TavianBarnes, I've incorporated your comment into my answer :)Stung
If the point is to be thread safe then you can't call strerror(errno) in the LC_GLOBAL_LOCALE case. What you can do is duplocale() and then pass it to strerror_l(). Full working example: github.com/tavianator/bfs/blob/…Crown
I misread "since an implementation may have extensions with new categories there should be a way to dup a locale and make modifications without specific mention of those extensions, otherwise a supposedly portable library could end up clobbering any extensions in the duped locale" at austin-group-l.opengroup.narkive.com/MFGftIBP/… to mean that duplocale wouldn't work. Are there scenarios where duplocale won't work? What are they?Stung
@DanielLe I believe the "clobbering any extensions in the duped locale" complaint was about the setlocale() + newlocale() + setlocale() + ... approach. That discussion led to this POSIX issue which defined the behaviour of duplocale(LC_GLOBAL_LOCALE): austingroupbugs.net/view.php?id=301Crown
E
4

If you pass "" to the locale parameter newlocale will allocate a locale object set to the current native locale[1]

[1]http://pubs.opengroup.org/onlinepubs/9699919799/functions/newlocale.html

static  locale_t locale;

bool MyStrerrorInit(void)
{
    locale = newlocale(LC_ALL_MASK,"",(locale_t)0);
    
    if (locale == (locale_t)0) {
       return false;
    }
 
    return true;
}
    
char * MyStrerror(int error)
{
    return strerror_l(error, locale);
}
Engage answered 15/1, 2015 at 15:29 Comment(1)
You can use LC_ALL_MASK instead of ORing all of the category masksCorridor

© 2022 - 2024 — McMap. All rights reserved.