(root)/
tar-1.35/
gnu/
time_rz.c
       1  /* Time zone functions such as tzalloc and localtime_rz
       2  
       3     Copyright 2015-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 3 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  /* Written by Paul Eggert.  */
      19  
      20  /* Although this module is not thread-safe, any races should be fairly
      21     rare and reasonably benign.  For complete thread-safety, use a C
      22     library with a working timezone_t type, so that this module is not
      23     needed.  */
      24  
      25  #include <config.h>
      26  
      27  #include <time.h>
      28  
      29  #include <errno.h>
      30  #include <stddef.h>
      31  #include <stdlib.h>
      32  #include <string.h>
      33  
      34  #include "flexmember.h"
      35  #include "idx.h"
      36  #include "time-internal.h"
      37  
      38  /* The approximate size to use for small allocation requests.  This is
      39     the largest "small" request for the GNU C library malloc.  */
      40  enum { DEFAULT_MXFAST = 64 * sizeof (size_t) / 4 };
      41  
      42  /* Minimum size of the ABBRS member of struct tm_zone.  ABBRS is larger
      43     only in the unlikely case where an abbreviation longer than this is
      44     used.  */
      45  enum { ABBR_SIZE_MIN = DEFAULT_MXFAST - offsetof (struct tm_zone, abbrs) };
      46  
      47  /* Magic cookie timezone_t value, for local time.  It differs from
      48     NULL and from all other timezone_t values.  Only the address
      49     matters; the pointer is never dereferenced.  */
      50  static timezone_t const local_tz = (timezone_t) 1;
      51  
      52  /* Copy to ABBRS the abbreviation at ABBR with size ABBR_SIZE (this
      53     includes its trailing null byte).  Append an extra null byte to
      54     mark the end of ABBRS.  */
      55  static void
      56  extend_abbrs (char *abbrs, char const *abbr, size_t abbr_size)
      57  {
      58    memcpy (abbrs, abbr, abbr_size);
      59    abbrs[abbr_size] = '\0';
      60  }
      61  
      62  /* Return a newly allocated time zone for NAME, or NULL on failure.
      63     A null NAME stands for wall clock time (which is like unset TZ).  */
      64  timezone_t
      65  tzalloc (char const *name)
      66  {
      67    size_t name_size = name ? strlen (name) + 1 : 0;
      68    size_t abbr_size = name_size < ABBR_SIZE_MIN ? ABBR_SIZE_MIN : name_size + 1;
      69    timezone_t tz = malloc (FLEXSIZEOF (struct tm_zone, abbrs, abbr_size));
      70    if (tz)
      71      {
      72        tz->next = NULL;
      73  #if HAVE_TZNAME && !HAVE_STRUCT_TM_TM_ZONE
      74        tz->tzname_copy[0] = tz->tzname_copy[1] = NULL;
      75  #endif
      76        tz->tz_is_set = !!name;
      77        tz->abbrs[0] = '\0';
      78        if (name)
      79          extend_abbrs (tz->abbrs, name, name_size);
      80      }
      81    return tz;
      82  }
      83  
      84  /* Save into TZ any nontrivial time zone abbreviation used by TM, and
      85     update *TM (if HAVE_STRUCT_TM_TM_ZONE) or *TZ (if
      86     !HAVE_STRUCT_TM_TM_ZONE && HAVE_TZNAME) if they use the abbreviation.
      87     Return true if successful, false (setting errno) otherwise.  */
      88  static bool
      89  save_abbr (timezone_t tz, struct tm *tm)
      90  {
      91  #if HAVE_STRUCT_TM_TM_ZONE || HAVE_TZNAME
      92    char const *zone = NULL;
      93    char *zone_copy = (char *) "";
      94  
      95  # if HAVE_TZNAME
      96    int tzname_index = -1;
      97  # endif
      98  
      99  # if HAVE_STRUCT_TM_TM_ZONE
     100    zone = tm->tm_zone;
     101  # endif
     102  
     103  # if HAVE_TZNAME
     104    if (! (zone && *zone) && 0 <= tm->tm_isdst)
     105      {
     106        tzname_index = tm->tm_isdst != 0;
     107        zone = tzname[tzname_index];
     108      }
     109  # endif
     110  
     111    /* No need to replace null zones, or zones within the struct tm.  */
     112    if (!zone || ((char *) tm <= zone && zone < (char *) (tm + 1)))
     113      return true;
     114  
     115    if (*zone)
     116      {
     117        zone_copy = tz->abbrs;
     118  
     119        while (strcmp (zone_copy, zone) != 0)
     120          {
     121            if (! (*zone_copy || (zone_copy == tz->abbrs && tz->tz_is_set)))
     122              {
     123                idx_t zone_size = strlen (zone) + 1;
     124                if (zone_size < tz->abbrs + ABBR_SIZE_MIN - zone_copy)
     125                  extend_abbrs (zone_copy, zone, zone_size);
     126                else
     127                  {
     128                    tz = tz->next = tzalloc (zone);
     129                    if (!tz)
     130                      return false;
     131                    tz->tz_is_set = 0;
     132                    zone_copy = tz->abbrs;
     133                  }
     134                break;
     135              }
     136  
     137            zone_copy += strlen (zone_copy) + 1;
     138            if (!*zone_copy && tz->next)
     139              {
     140                tz = tz->next;
     141                zone_copy = tz->abbrs;
     142              }
     143          }
     144      }
     145  
     146    /* Replace the zone name so that its lifetime matches that of TZ.  */
     147  # if HAVE_STRUCT_TM_TM_ZONE
     148    tm->tm_zone = zone_copy;
     149  # else
     150    if (0 <= tzname_index)
     151      tz->tzname_copy[tzname_index] = zone_copy;
     152  # endif
     153  #endif
     154  
     155    return true;
     156  }
     157  
     158  /* Free a time zone.  */
     159  void
     160  tzfree (timezone_t tz)
     161  {
     162    if (tz != local_tz)
     163      while (tz)
     164        {
     165          timezone_t next = tz->next;
     166          free (tz);
     167          tz = next;
     168        }
     169  }
     170  
     171  /* Get and set the TZ environment variable.  These functions can be
     172     overridden by programs like Emacs that manage their own environment.  */
     173  
     174  #ifndef getenv_TZ
     175  static char *
     176  getenv_TZ (void)
     177  {
     178    return getenv ("TZ");
     179  }
     180  #endif
     181  
     182  #ifndef setenv_TZ
     183  static int
     184  setenv_TZ (char const *tz)
     185  {
     186    return tz ? setenv ("TZ", tz, 1) : unsetenv ("TZ");
     187  }
     188  #endif
     189  
     190  /* Change the environment to match the specified timezone_t value.
     191     Return true if successful, false (setting errno) otherwise.  */
     192  static bool
     193  change_env (timezone_t tz)
     194  {
     195    if (setenv_TZ (tz->tz_is_set ? tz->abbrs : NULL) != 0)
     196      return false;
     197    tzset ();
     198    return true;
     199  }
     200  
     201  /* Temporarily set the time zone to TZ, which must not be null.
     202     Return LOCAL_TZ if the time zone setting is already correct.
     203     Otherwise return a newly allocated time zone representing the old
     204     setting, or NULL (setting errno) on failure.  */
     205  static timezone_t
     206  set_tz (timezone_t tz)
     207  {
     208    char *env_tz = getenv_TZ ();
     209    if (env_tz
     210        ? tz->tz_is_set && strcmp (tz->abbrs, env_tz) == 0
     211        : !tz->tz_is_set)
     212      return local_tz;
     213    else
     214      {
     215        timezone_t old_tz = tzalloc (env_tz);
     216        if (!old_tz)
     217          return old_tz;
     218        if (! change_env (tz))
     219          {
     220            int saved_errno = errno;
     221            tzfree (old_tz);
     222            errno = saved_errno;
     223            return NULL;
     224          }
     225        return old_tz;
     226      }
     227  }
     228  
     229  /* Restore an old setting returned by set_tz.  It must not be null.
     230     Return true (preserving errno) if successful, false (setting errno)
     231     otherwise.  */
     232  static bool
     233  revert_tz (timezone_t tz)
     234  {
     235    if (tz == local_tz)
     236      return true;
     237    else
     238      {
     239        int saved_errno = errno;
     240        bool ok = change_env (tz);
     241        if (!ok)
     242          saved_errno = errno;
     243        tzfree (tz);
     244        errno = saved_errno;
     245        return ok;
     246      }
     247  }
     248  
     249  /* Use time zone TZ to compute localtime_r (T, TM).  */
     250  struct tm *
     251  localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
     252  {
     253  #ifdef HAVE_LOCALTIME_INFLOOP_BUG
     254    /* The -67768038400665599 comes from:
     255       https://lists.gnu.org/r/bug-gnulib/2017-07/msg00142.html
     256       On affected platforms the greatest POSIX-compatible time_t value
     257       that could return nonnull is 67768036191766798 (when
     258       TZ="XXX24:59:59" it resolves to the year 2**31 - 1 + 1900, on
     259       12-31 at 23:59:59), so test for that too while we're in the
     260       neighborhood.  */
     261    if (! (-67768038400665599 <= *t && *t <= 67768036191766798))
     262      {
     263        errno = EOVERFLOW;
     264        return NULL;
     265      }
     266  #endif
     267  
     268    if (!tz)
     269      return gmtime_r (t, tm);
     270    else
     271      {
     272        timezone_t old_tz = set_tz (tz);
     273        if (old_tz)
     274          {
     275            bool abbr_saved = localtime_r (t, tm) && save_abbr (tz, tm);
     276            if (revert_tz (old_tz) && abbr_saved)
     277              return tm;
     278          }
     279        return NULL;
     280      }
     281  }
     282  
     283  /* Use time zone TZ to compute mktime (TM).  */
     284  time_t
     285  mktime_z (timezone_t tz, struct tm *tm)
     286  {
     287    if (!tz)
     288      return timegm (tm);
     289    else
     290      {
     291        timezone_t old_tz = set_tz (tz);
     292        if (old_tz)
     293          {
     294            struct tm tm_1;
     295            tm_1.tm_sec = tm->tm_sec;
     296            tm_1.tm_min = tm->tm_min;
     297            tm_1.tm_hour = tm->tm_hour;
     298            tm_1.tm_mday = tm->tm_mday;
     299            tm_1.tm_mon = tm->tm_mon;
     300            tm_1.tm_year = tm->tm_year;
     301            tm_1.tm_yday = -1;
     302            tm_1.tm_isdst = tm->tm_isdst;
     303            time_t t = mktime (&tm_1);
     304            bool ok = 0 <= tm_1.tm_yday;
     305  #if HAVE_STRUCT_TM_TM_ZONE || HAVE_TZNAME
     306            ok = ok && save_abbr (tz, &tm_1);
     307  #endif
     308            if (revert_tz (old_tz) && ok)
     309              {
     310                *tm = tm_1;
     311                return t;
     312              }
     313          }
     314        return -1;
     315      }
     316  }