(root)/
tar-1.35/
gnu/
nl_langinfo.c
       1  /* nl_langinfo() replacement: query locale dependent information.
       2  
       3     Copyright (C) 2007-2023 Free Software Foundation, Inc.
       4  
       5     This file is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU Lesser General Public License as
       7     published by the Free Software Foundation; either version 2.1 of the
       8     License, or (at your option) any later version.
       9  
      10     This file is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <config.h>
      19  
      20  /* Specification.  */
      21  #include <langinfo.h>
      22  
      23  #include <locale.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  #if defined _WIN32 && ! defined __CYGWIN__
      27  # define WIN32_LEAN_AND_MEAN  /* avoid including junk */
      28  # include <windows.h>
      29  # include <stdio.h>
      30  #endif
      31  
      32  #if REPLACE_NL_LANGINFO && !NL_LANGINFO_MTSAFE
      33  # if defined _WIN32 && !defined __CYGWIN__
      34  
      35  #  define WIN32_LEAN_AND_MEAN  /* avoid including junk */
      36  #  include <windows.h>
      37  
      38  # elif HAVE_PTHREAD_API
      39  
      40  #  include <pthread.h>
      41  #  if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
      42  #   include <threads.h>
      43  #   pragma weak thrd_exit
      44  #   define c11_threads_in_use() (thrd_exit != NULL)
      45  #  else
      46  #   define c11_threads_in_use() 0
      47  #  endif
      48  
      49  # elif HAVE_THREADS_H
      50  
      51  #  include <threads.h>
      52  
      53  # endif
      54  #endif
      55  
      56  /* nl_langinfo() must be multithread-safe.  To achieve this without using
      57     thread-local storage:
      58       1. We use a specific static buffer for each possible argument.
      59          So that different threads can call nl_langinfo with different arguments,
      60          without interfering.
      61       2. We use a simple strcpy or memcpy to fill this static buffer.  Filling it
      62          through, for example, strcpy + strcat would not be guaranteed to leave
      63          the buffer's contents intact if another thread is currently accessing
      64          it.  If necessary, the contents is first assembled in a stack-allocated
      65          buffer.  */
      66  
      67  #if !REPLACE_NL_LANGINFO || GNULIB_defined_CODESET
      68  /* Return the codeset of the current locale, if this is easily deducible.
      69     Otherwise, return "".  */
      70  static char *
      71  ctype_codeset (void)
      72  {
      73    /* This function is only used on platforms which don't have uselocale().
      74       Therefore we don't need to look at the per-thread locale first, here.  */
      75    static char result[2 + 10 + 1];
      76    char buf[2 + 10 + 1];
      77    char locale[SETLOCALE_NULL_MAX];
      78    char *codeset;
      79    size_t codesetlen;
      80  
      81    if (setlocale_null_r (LC_CTYPE, locale, sizeof (locale)))
      82      locale[0] = '\0';
      83  
      84    codeset = buf;
      85    codeset[0] = '\0';
      86  
      87    if (locale[0])
      88      {
      89        /* If the locale name contains an encoding after the dot, return it.  */
      90        char *dot = strchr (locale, '.');
      91  
      92        if (dot)
      93          {
      94            /* Look for the possible @... trailer and remove it, if any.  */
      95            char *codeset_start = dot + 1;
      96            char const *modifier = strchr (codeset_start, '@');
      97  
      98            if (! modifier)
      99              codeset = codeset_start;
     100            else
     101              {
     102                codesetlen = modifier - codeset_start;
     103                if (codesetlen < sizeof buf)
     104                  {
     105                    codeset = memcpy (buf, codeset_start, codesetlen);
     106                    codeset[codesetlen] = '\0';
     107                  }
     108              }
     109          }
     110      }
     111  
     112  # if defined _WIN32 && ! defined __CYGWIN__
     113    /* If setlocale is successful, it returns the number of the
     114       codepage, as a string.  Otherwise, fall back on Windows API
     115       GetACP, which returns the locale's codepage as a number (although
     116       this doesn't change according to what the 'setlocale' call specified).
     117       Either way, prepend "CP" to make it a valid codeset name.  */
     118    codesetlen = strlen (codeset);
     119    if (0 < codesetlen && codesetlen < sizeof buf - 2)
     120      memmove (buf + 2, codeset, codesetlen + 1);
     121    else
     122      sprintf (buf + 2, "%u", GetACP ());
     123    /* For a locale name such as "French_France.65001", in Windows 10,
     124       setlocale now returns "French_France.utf8" instead.  */
     125    if (strcmp (buf + 2, "65001") == 0 || strcmp (buf + 2, "utf8") == 0)
     126      return (char *) "UTF-8";
     127    else
     128      {
     129        memcpy (buf, "CP", 2);
     130        strcpy (result, buf);
     131        return result;
     132      }
     133  # else
     134    strcpy (result, codeset);
     135    return result;
     136  #endif
     137  }
     138  #endif
     139  
     140  
     141  #if REPLACE_NL_LANGINFO
     142  
     143  /* Override nl_langinfo with support for added nl_item values.  */
     144  
     145  # undef nl_langinfo
     146  
     147  /* Without locking, on Solaris 11.3, test-nl_langinfo-mt fails, with message
     148     "thread5 disturbed by threadN!", even when threadN invokes only
     149        nl_langinfo (CODESET);
     150        nl_langinfo (CRNCYSTR);
     151     Similarly on Solaris 10.  */
     152  
     153  # if !NL_LANGINFO_MTSAFE /* Solaris */
     154  
     155  #  define ITEMS (MAXSTRMSG + 1)
     156  #  define MAX_RESULT_LEN 80
     157  
     158  static char *
     159  nl_langinfo_unlocked (nl_item item)
     160  {
     161    static char result[ITEMS][MAX_RESULT_LEN];
     162  
     163    /* The result of nl_langinfo is in storage that can be overwritten by
     164       other calls to nl_langinfo.  */
     165    char *tmp = nl_langinfo (item);
     166    if (item >= 0 && item < ITEMS && tmp != NULL)
     167      {
     168        size_t tmp_len = strlen (tmp);
     169        if (tmp_len < MAX_RESULT_LEN)
     170          strcpy (result[item], tmp);
     171        else
     172          {
     173            /* Produce a truncated result.  Oh well...  */
     174            result[item][MAX_RESULT_LEN - 1] = '\0';
     175            memcpy (result[item], tmp, MAX_RESULT_LEN - 1);
     176          }
     177        return result[item];
     178      }
     179    else
     180      return tmp;
     181  }
     182  
     183  /* Use a lock, so that no two threads can invoke nl_langinfo_unlocked
     184     at the same time.  */
     185  
     186  /* Prohibit renaming this symbol.  */
     187  #  undef gl_get_nl_langinfo_lock
     188  
     189  #  if defined _WIN32 && !defined __CYGWIN__
     190  
     191  extern __declspec(dllimport) CRITICAL_SECTION *gl_get_nl_langinfo_lock (void);
     192  
     193  static char *
     194  nl_langinfo_with_lock (nl_item item)
     195  {
     196    CRITICAL_SECTION *lock = gl_get_nl_langinfo_lock ();
     197    char *ret;
     198  
     199    EnterCriticalSection (lock);
     200    ret = nl_langinfo_unlocked (item);
     201    LeaveCriticalSection (lock);
     202  
     203    return ret;
     204  }
     205  
     206  #  elif HAVE_PTHREAD_API
     207  
     208  extern
     209  #   if defined _WIN32 || defined __CYGWIN__
     210    __declspec(dllimport)
     211  #   endif
     212    pthread_mutex_t *gl_get_nl_langinfo_lock (void);
     213  
     214  #   if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
     215  
     216       /* Avoid the need to link with '-lpthread'.  */
     217  #    pragma weak pthread_mutex_lock
     218  #    pragma weak pthread_mutex_unlock
     219  
     220       /* Determine whether libpthread is in use.  */
     221  #    pragma weak pthread_mutexattr_gettype
     222       /* See the comments in lock.h.  */
     223  #    define pthread_in_use() \
     224         (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
     225  
     226  #   else
     227  #    define pthread_in_use() 1
     228  #   endif
     229  
     230  static char *
     231  nl_langinfo_with_lock (nl_item item)
     232  {
     233    if (pthread_in_use())
     234      {
     235        pthread_mutex_t *lock = gl_get_nl_langinfo_lock ();
     236        char *ret;
     237  
     238        if (pthread_mutex_lock (lock))
     239          abort ();
     240        ret = nl_langinfo_unlocked (item);
     241        if (pthread_mutex_unlock (lock))
     242          abort ();
     243  
     244        return ret;
     245      }
     246    else
     247      return nl_langinfo_unlocked (item);
     248  }
     249  
     250  #  elif HAVE_THREADS_H
     251  
     252  extern mtx_t *gl_get_nl_langinfo_lock (void);
     253  
     254  static char *
     255  nl_langinfo_with_lock (nl_item item)
     256  {
     257    mtx_t *lock = gl_get_nl_langinfo_lock ();
     258    char *ret;
     259  
     260    if (mtx_lock (lock) != thrd_success)
     261      abort ();
     262    ret = nl_langinfo_unlocked (item);
     263    if (mtx_unlock (lock) != thrd_success)
     264      abort ();
     265  
     266    return ret;
     267  }
     268  
     269  #  endif
     270  
     271  # else
     272  
     273  /* On other platforms, no lock is needed.  */
     274  #  define nl_langinfo_with_lock nl_langinfo
     275  
     276  # endif
     277  
     278  char *
     279  rpl_nl_langinfo (nl_item item)
     280  {
     281    switch (item)
     282      {
     283  # if GNULIB_defined_CODESET
     284      case CODESET:
     285        return ctype_codeset ();
     286  # endif
     287  # if GNULIB_defined_T_FMT_AMPM
     288      case T_FMT_AMPM:
     289        return (char *) "%I:%M:%S %p";
     290  # endif
     291  # if GNULIB_defined_ALTMON
     292      case ALTMON_1:
     293      case ALTMON_2:
     294      case ALTMON_3:
     295      case ALTMON_4:
     296      case ALTMON_5:
     297      case ALTMON_6:
     298      case ALTMON_7:
     299      case ALTMON_8:
     300      case ALTMON_9:
     301      case ALTMON_10:
     302      case ALTMON_11:
     303      case ALTMON_12:
     304        /* We don't ship the appropriate localizations with gnulib.  Therefore,
     305           treat ALTMON_i like MON_i.  */
     306        item = item - ALTMON_1 + MON_1;
     307        break;
     308  # endif
     309  # if GNULIB_defined_ERA
     310      case ERA:
     311        /* The format is not standardized.  In glibc it is a sequence of strings
     312           of the form "direction:offset:start_date:end_date:era_name:era_format"
     313           with an empty string at the end.  */
     314        return (char *) "";
     315      case ERA_D_FMT:
     316        /* The %Ex conversion in strftime behaves like %x if the locale does not
     317           have an alternative time format.  */
     318        item = D_FMT;
     319        break;
     320      case ERA_D_T_FMT:
     321        /* The %Ec conversion in strftime behaves like %c if the locale does not
     322           have an alternative time format.  */
     323        item = D_T_FMT;
     324        break;
     325      case ERA_T_FMT:
     326        /* The %EX conversion in strftime behaves like %X if the locale does not
     327           have an alternative time format.  */
     328        item = T_FMT;
     329        break;
     330      case ALT_DIGITS:
     331        /* The format is not standardized.  In glibc it is a sequence of 10
     332           strings, appended in memory.  */
     333        return (char *) "\0\0\0\0\0\0\0\0\0\0";
     334  # endif
     335  # if GNULIB_defined_YESEXPR || !FUNC_NL_LANGINFO_YESEXPR_WORKS
     336      case YESEXPR:
     337        return (char *) "^[yY]";
     338      case NOEXPR:
     339        return (char *) "^[nN]";
     340  # endif
     341      default:
     342        break;
     343      }
     344    return nl_langinfo_with_lock (item);
     345  }
     346  
     347  #else
     348  
     349  /* Provide nl_langinfo from scratch, either for native MS-Windows, or
     350     for old Unix platforms without locales, such as Linux libc5 or
     351     BeOS.  */
     352  
     353  # include <time.h>
     354  
     355  char *
     356  nl_langinfo (nl_item item)
     357  {
     358    char buf[100];
     359    struct tm tmm = { 0 };
     360  
     361    switch (item)
     362      {
     363      /* nl_langinfo items of the LC_CTYPE category */
     364      case CODESET:
     365        {
     366          char *codeset = ctype_codeset ();
     367          if (*codeset)
     368            return codeset;
     369        }
     370  # ifdef __BEOS__
     371        return (char *) "UTF-8";
     372  # else
     373        return (char *) "ISO-8859-1";
     374  # endif
     375      /* nl_langinfo items of the LC_NUMERIC category */
     376      case RADIXCHAR:
     377        return localeconv () ->decimal_point;
     378      case THOUSEP:
     379        return localeconv () ->thousands_sep;
     380  # ifdef GROUPING
     381      case GROUPING:
     382        return localeconv () ->grouping;
     383  # endif
     384      /* nl_langinfo items of the LC_TIME category.
     385         TODO: Really use the locale.  */
     386      case D_T_FMT:
     387      case ERA_D_T_FMT:
     388        return (char *) "%a %b %e %H:%M:%S %Y";
     389      case D_FMT:
     390      case ERA_D_FMT:
     391        return (char *) "%m/%d/%y";
     392      case T_FMT:
     393      case ERA_T_FMT:
     394        return (char *) "%H:%M:%S";
     395      case T_FMT_AMPM:
     396        return (char *) "%I:%M:%S %p";
     397      case AM_STR:
     398        {
     399          static char result[80];
     400          if (!strftime (buf, sizeof result, "%p", &tmm))
     401            return (char *) "AM";
     402          strcpy (result, buf);
     403          return result;
     404        }
     405      case PM_STR:
     406        {
     407          static char result[80];
     408          tmm.tm_hour = 12;
     409          if (!strftime (buf, sizeof result, "%p", &tmm))
     410            return (char *) "PM";
     411          strcpy (result, buf);
     412          return result;
     413        }
     414      case DAY_1:
     415      case DAY_2:
     416      case DAY_3:
     417      case DAY_4:
     418      case DAY_5:
     419      case DAY_6:
     420      case DAY_7:
     421        {
     422          static char result[7][50];
     423          static char const days[][sizeof "Wednesday"] = {
     424            "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
     425            "Friday", "Saturday"
     426          };
     427          tmm.tm_wday = item - DAY_1;
     428          if (!strftime (buf, sizeof result[0], "%A", &tmm))
     429            return (char *) days[item - DAY_1];
     430          strcpy (result[item - DAY_1], buf);
     431          return result[item - DAY_1];
     432        }
     433      case ABDAY_1:
     434      case ABDAY_2:
     435      case ABDAY_3:
     436      case ABDAY_4:
     437      case ABDAY_5:
     438      case ABDAY_6:
     439      case ABDAY_7:
     440        {
     441          static char result[7][30];
     442          static char const abdays[][sizeof "Sun"] = {
     443            "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
     444          };
     445          tmm.tm_wday = item - ABDAY_1;
     446          if (!strftime (buf, sizeof result[0], "%a", &tmm))
     447            return (char *) abdays[item - ABDAY_1];
     448          strcpy (result[item - ABDAY_1], buf);
     449          return result[item - ABDAY_1];
     450        }
     451      {
     452        static char const months[][sizeof "September"] = {
     453          "January", "February", "March", "April", "May", "June", "July",
     454          "September", "October", "November", "December"
     455        };
     456        case MON_1:
     457        case MON_2:
     458        case MON_3:
     459        case MON_4:
     460        case MON_5:
     461        case MON_6:
     462        case MON_7:
     463        case MON_8:
     464        case MON_9:
     465        case MON_10:
     466        case MON_11:
     467        case MON_12:
     468          {
     469            static char result[12][50];
     470            tmm.tm_mon = item - MON_1;
     471            if (!strftime (buf, sizeof result[0], "%B", &tmm))
     472              return (char *) months[item - MON_1];
     473            strcpy (result[item - MON_1], buf);
     474            return result[item - MON_1];
     475          }
     476        case ALTMON_1:
     477        case ALTMON_2:
     478        case ALTMON_3:
     479        case ALTMON_4:
     480        case ALTMON_5:
     481        case ALTMON_6:
     482        case ALTMON_7:
     483        case ALTMON_8:
     484        case ALTMON_9:
     485        case ALTMON_10:
     486        case ALTMON_11:
     487        case ALTMON_12:
     488          {
     489            static char result[12][50];
     490            tmm.tm_mon = item - ALTMON_1;
     491            /* The platforms without nl_langinfo() don't support strftime with
     492               %OB.  We don't even need to try.  */
     493            #if 0
     494            if (!strftime (buf, sizeof result[0], "%OB", &tmm))
     495            #endif
     496              if (!strftime (buf, sizeof result[0], "%B", &tmm))
     497                return (char *) months[item - ALTMON_1];
     498            strcpy (result[item - ALTMON_1], buf);
     499            return result[item - ALTMON_1];
     500          }
     501      }
     502      case ABMON_1:
     503      case ABMON_2:
     504      case ABMON_3:
     505      case ABMON_4:
     506      case ABMON_5:
     507      case ABMON_6:
     508      case ABMON_7:
     509      case ABMON_8:
     510      case ABMON_9:
     511      case ABMON_10:
     512      case ABMON_11:
     513      case ABMON_12:
     514        {
     515          static char result[12][30];
     516          static char const abmonths[][sizeof "Jan"] = {
     517            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
     518            "Sep", "Oct", "Nov", "Dec"
     519          };
     520          tmm.tm_mon = item - ABMON_1;
     521          if (!strftime (buf, sizeof result[0], "%b", &tmm))
     522            return (char *) abmonths[item - ABMON_1];
     523          strcpy (result[item - ABMON_1], buf);
     524          return result[item - ABMON_1];
     525        }
     526      case ERA:
     527        return (char *) "";
     528      case ALT_DIGITS:
     529        return (char *) "\0\0\0\0\0\0\0\0\0\0";
     530      /* nl_langinfo items of the LC_MONETARY category.  */
     531      case CRNCYSTR:
     532        return localeconv () ->currency_symbol;
     533  # ifdef INT_CURR_SYMBOL
     534      case INT_CURR_SYMBOL:
     535        return localeconv () ->int_curr_symbol;
     536      case MON_DECIMAL_POINT:
     537        return localeconv () ->mon_decimal_point;
     538      case MON_THOUSANDS_SEP:
     539        return localeconv () ->mon_thousands_sep;
     540      case MON_GROUPING:
     541        return localeconv () ->mon_grouping;
     542      case POSITIVE_SIGN:
     543        return localeconv () ->positive_sign;
     544      case NEGATIVE_SIGN:
     545        return localeconv () ->negative_sign;
     546      case FRAC_DIGITS:
     547        return & localeconv () ->frac_digits;
     548      case INT_FRAC_DIGITS:
     549        return & localeconv () ->int_frac_digits;
     550      case P_CS_PRECEDES:
     551        return & localeconv () ->p_cs_precedes;
     552      case N_CS_PRECEDES:
     553        return & localeconv () ->n_cs_precedes;
     554      case P_SEP_BY_SPACE:
     555        return & localeconv () ->p_sep_by_space;
     556      case N_SEP_BY_SPACE:
     557        return & localeconv () ->n_sep_by_space;
     558      case P_SIGN_POSN:
     559        return & localeconv () ->p_sign_posn;
     560      case N_SIGN_POSN:
     561        return & localeconv () ->n_sign_posn;
     562  # endif
     563      /* nl_langinfo items of the LC_MESSAGES category
     564         TODO: Really use the locale. */
     565      case YESEXPR:
     566        return (char *) "^[yY]";
     567      case NOEXPR:
     568        return (char *) "^[nN]";
     569      default:
     570        return (char *) "";
     571      }
     572  }
     573  
     574  #endif