Blame lib/time_rz.c

Packit 8f70b4
/* Time zone functions such as tzalloc and localtime_rz
Packit 8f70b4
Packit 8f70b4
   Copyright 2015-2018 Free Software Foundation, Inc.
Packit 8f70b4
Packit 8f70b4
   This program is free software; you can redistribute it and/or modify
Packit 8f70b4
   it under the terms of the GNU General Public License as published by
Packit 8f70b4
   the Free Software Foundation; either version 3, or (at your option)
Packit 8f70b4
   any later version.
Packit 8f70b4
Packit 8f70b4
   This program is distributed in the hope that it will be useful,
Packit 8f70b4
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
   GNU General Public License for more details.
Packit 8f70b4
Packit 8f70b4
   You should have received a copy of the GNU General Public License along
Packit 8f70b4
   with this program; if not, see <https://www.gnu.org/licenses/>.  */
Packit 8f70b4
Packit 8f70b4
/* Written by Paul Eggert.  */
Packit 8f70b4
Packit 8f70b4
/* Although this module is not thread-safe, any races should be fairly
Packit 8f70b4
   rare and reasonably benign.  For complete thread-safety, use a C
Packit 8f70b4
   library with a working timezone_t type, so that this module is not
Packit 8f70b4
   needed.  */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
#include <time.h>
Packit 8f70b4
Packit 8f70b4
#include <errno.h>
Packit 8f70b4
#include <limits.h>
Packit 8f70b4
#include <stdbool.h>
Packit 8f70b4
#include <stddef.h>
Packit 8f70b4
#include <stdlib.h>
Packit 8f70b4
#include <string.h>
Packit 8f70b4
Packit 8f70b4
#include "flexmember.h"
Packit 8f70b4
#include "time-internal.h"
Packit 8f70b4
Packit 8f70b4
#ifndef SIZE_MAX
Packit 8f70b4
# define SIZE_MAX ((size_t) -1)
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* The approximate size to use for small allocation requests.  This is
Packit 8f70b4
   the largest "small" request for the GNU C library malloc.  */
Packit 8f70b4
enum { DEFAULT_MXFAST = 64 * sizeof (size_t) / 4 };
Packit 8f70b4
Packit 8f70b4
/* Minimum size of the ABBRS member of struct tm_zone.  ABBRS is larger
Packit 8f70b4
   only in the unlikely case where an abbreviation longer than this is
Packit 8f70b4
   used.  */
Packit 8f70b4
enum { ABBR_SIZE_MIN = DEFAULT_MXFAST - offsetof (struct tm_zone, abbrs) };
Packit 8f70b4
Packit 8f70b4
/* Magic cookie timezone_t value, for local time.  It differs from
Packit 8f70b4
   NULL and from all other timezone_t values.  Only the address
Packit 8f70b4
   matters; the pointer is never dereferenced.  */
Packit 8f70b4
static timezone_t const local_tz = (timezone_t) 1;
Packit 8f70b4
Packit 8f70b4
#if HAVE_TM_ZONE || HAVE_TZNAME
Packit 8f70b4
Packit 8f70b4
/* Return true if the values A and B differ according to the rules for
Packit 8f70b4
   tm_isdst: A and B differ if one is zero and the other positive.  */
Packit 8f70b4
static bool
Packit 8f70b4
isdst_differ (int a, int b)
Packit 8f70b4
{
Packit 8f70b4
  return !a != !b && 0 <= a && 0 <= b;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return true if A and B are equal.  */
Packit 8f70b4
static int
Packit 8f70b4
equal_tm (const struct tm *a, const struct tm *b)
Packit 8f70b4
{
Packit 8f70b4
  return ! ((a->tm_sec ^ b->tm_sec)
Packit 8f70b4
            | (a->tm_min ^ b->tm_min)
Packit 8f70b4
            | (a->tm_hour ^ b->tm_hour)
Packit 8f70b4
            | (a->tm_mday ^ b->tm_mday)
Packit 8f70b4
            | (a->tm_mon ^ b->tm_mon)
Packit 8f70b4
            | (a->tm_year ^ b->tm_year)
Packit 8f70b4
            | isdst_differ (a->tm_isdst, b->tm_isdst));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Copy to ABBRS the abbreviation at ABBR with size ABBR_SIZE (this
Packit 8f70b4
   includes its trailing null byte).  Append an extra null byte to
Packit 8f70b4
   mark the end of ABBRS.  */
Packit 8f70b4
static void
Packit 8f70b4
extend_abbrs (char *abbrs, char const *abbr, size_t abbr_size)
Packit 8f70b4
{
Packit 8f70b4
  memcpy (abbrs, abbr, abbr_size);
Packit 8f70b4
  abbrs[abbr_size] = '\0';
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return a newly allocated time zone for NAME, or NULL on failure.
Packit 8f70b4
   A null NAME stands for wall clock time (which is like unset TZ).  */
Packit 8f70b4
timezone_t
Packit 8f70b4
tzalloc (char const *name)
Packit 8f70b4
{
Packit 8f70b4
  size_t name_size = name ? strlen (name) + 1 : 0;
Packit 8f70b4
  size_t abbr_size = name_size < ABBR_SIZE_MIN ? ABBR_SIZE_MIN : name_size + 1;
Packit 8f70b4
  timezone_t tz = malloc (FLEXSIZEOF (struct tm_zone, abbrs, abbr_size));
Packit 8f70b4
  if (tz)
Packit 8f70b4
    {
Packit 8f70b4
      tz->next = NULL;
Packit 8f70b4
#if HAVE_TZNAME && !HAVE_TM_ZONE
Packit 8f70b4
      tz->tzname_copy[0] = tz->tzname_copy[1] = NULL;
Packit 8f70b4
#endif
Packit 8f70b4
      tz->tz_is_set = !!name;
Packit 8f70b4
      tz->abbrs[0] = '\0';
Packit 8f70b4
      if (name)
Packit 8f70b4
        extend_abbrs (tz->abbrs, name, name_size);
Packit 8f70b4
    }
Packit 8f70b4
  return tz;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Save into TZ any nontrivial time zone abbreviation used by TM, and
Packit 8f70b4
   update *TM (if HAVE_TM_ZONE) or *TZ (if !HAVE_TM_ZONE &&
Packit 8f70b4
   HAVE_TZNAME) if they use the abbreviation.  Return true if
Packit 8f70b4
   successful, false (setting errno) otherwise.  */
Packit 8f70b4
static bool
Packit 8f70b4
save_abbr (timezone_t tz, struct tm *tm)
Packit 8f70b4
{
Packit 8f70b4
#if HAVE_TM_ZONE || HAVE_TZNAME
Packit 8f70b4
  char const *zone = NULL;
Packit 8f70b4
  char *zone_copy = (char *) "";
Packit 8f70b4
Packit 8f70b4
# if HAVE_TZNAME
Packit 8f70b4
  int tzname_index = -1;
Packit 8f70b4
# endif
Packit 8f70b4
Packit 8f70b4
# if HAVE_TM_ZONE
Packit 8f70b4
  zone = tm->tm_zone;
Packit 8f70b4
# endif
Packit 8f70b4
Packit 8f70b4
# if HAVE_TZNAME
Packit 8f70b4
  if (! (zone && *zone) && 0 <= tm->tm_isdst)
Packit 8f70b4
    {
Packit 8f70b4
      tzname_index = tm->tm_isdst != 0;
Packit 8f70b4
      zone = tzname[tzname_index];
Packit 8f70b4
    }
Packit 8f70b4
# endif
Packit 8f70b4
Packit 8f70b4
  /* No need to replace null zones, or zones within the struct tm.  */
Packit 8f70b4
  if (!zone || ((char *) tm <= zone && zone < (char *) (tm + 1)))
Packit 8f70b4
    return true;
Packit 8f70b4
Packit 8f70b4
  if (*zone)
Packit 8f70b4
    {
Packit 8f70b4
      zone_copy = tz->abbrs;
Packit 8f70b4
Packit 8f70b4
      while (strcmp (zone_copy, zone) != 0)
Packit 8f70b4
        {
Packit 8f70b4
          if (! (*zone_copy || (zone_copy == tz->abbrs && tz->tz_is_set)))
Packit 8f70b4
            {
Packit 8f70b4
              size_t zone_size = strlen (zone) + 1;
Packit 8f70b4
              size_t zone_used = zone_copy - tz->abbrs;
Packit 8f70b4
              if (SIZE_MAX - zone_used < zone_size)
Packit 8f70b4
                {
Packit 8f70b4
                  errno = ENOMEM;
Packit 8f70b4
                  return false;
Packit 8f70b4
                }
Packit 8f70b4
              if (zone_used + zone_size < ABBR_SIZE_MIN)
Packit 8f70b4
                extend_abbrs (zone_copy, zone, zone_size);
Packit 8f70b4
              else
Packit 8f70b4
                {
Packit 8f70b4
                  tz = tz->next = tzalloc (zone);
Packit 8f70b4
                  if (!tz)
Packit 8f70b4
                    return false;
Packit 8f70b4
                  tz->tz_is_set = 0;
Packit 8f70b4
                  zone_copy = tz->abbrs;
Packit 8f70b4
                }
Packit 8f70b4
              break;
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          zone_copy += strlen (zone_copy) + 1;
Packit 8f70b4
          if (!*zone_copy && tz->next)
Packit 8f70b4
            {
Packit 8f70b4
              tz = tz->next;
Packit 8f70b4
              zone_copy = tz->abbrs;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Replace the zone name so that its lifetime matches that of TZ.  */
Packit 8f70b4
# if HAVE_TM_ZONE
Packit 8f70b4
  tm->tm_zone = zone_copy;
Packit 8f70b4
# else
Packit 8f70b4
  if (0 <= tzname_index)
Packit 8f70b4
    tz->tzname_copy[tzname_index] = zone_copy;
Packit 8f70b4
# endif
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Free a time zone.  */
Packit 8f70b4
void
Packit 8f70b4
tzfree (timezone_t tz)
Packit 8f70b4
{
Packit 8f70b4
  if (tz != local_tz)
Packit 8f70b4
    while (tz)
Packit 8f70b4
      {
Packit 8f70b4
        timezone_t next = tz->next;
Packit 8f70b4
        free (tz);
Packit 8f70b4
        tz = next;
Packit 8f70b4
      }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Get and set the TZ environment variable.  These functions can be
Packit 8f70b4
   overridden by programs like Emacs that manage their own environment.  */
Packit 8f70b4
Packit 8f70b4
#ifndef getenv_TZ
Packit 8f70b4
static char *
Packit 8f70b4
getenv_TZ (void)
Packit 8f70b4
{
Packit 8f70b4
  return getenv ("TZ");
Packit 8f70b4
}
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#ifndef setenv_TZ
Packit 8f70b4
static int
Packit 8f70b4
setenv_TZ (char const *tz)
Packit 8f70b4
{
Packit 8f70b4
  return tz ? setenv ("TZ", tz, 1) : unsetenv ("TZ");
Packit 8f70b4
}
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Change the environment to match the specified timezone_t value.
Packit 8f70b4
   Return true if successful, false (setting errno) otherwise.  */
Packit 8f70b4
static bool
Packit 8f70b4
change_env (timezone_t tz)
Packit 8f70b4
{
Packit 8f70b4
  if (setenv_TZ (tz->tz_is_set ? tz->abbrs : NULL) != 0)
Packit 8f70b4
    return false;
Packit 8f70b4
  tzset ();
Packit 8f70b4
  return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Temporarily set the time zone to TZ, which must not be null.
Packit 8f70b4
   Return LOCAL_TZ if the time zone setting is already correct.
Packit 8f70b4
   Otherwise return a newly allocated time zone representing the old
Packit 8f70b4
   setting, or NULL (setting errno) on failure.  */
Packit 8f70b4
static timezone_t
Packit 8f70b4
set_tz (timezone_t tz)
Packit 8f70b4
{
Packit 8f70b4
  char *env_tz = getenv_TZ ();
Packit 8f70b4
  if (env_tz
Packit 8f70b4
      ? tz->tz_is_set && strcmp (tz->abbrs, env_tz) == 0
Packit 8f70b4
      : !tz->tz_is_set)
Packit 8f70b4
    return local_tz;
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      timezone_t old_tz = tzalloc (env_tz);
Packit 8f70b4
      if (!old_tz)
Packit 8f70b4
        return old_tz;
Packit 8f70b4
      if (! change_env (tz))
Packit 8f70b4
        {
Packit 8f70b4
          int saved_errno = errno;
Packit 8f70b4
          tzfree (old_tz);
Packit 8f70b4
          errno = saved_errno;
Packit 8f70b4
          return NULL;
Packit 8f70b4
        }
Packit 8f70b4
      return old_tz;
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Restore an old setting returned by set_tz.  It must not be null.
Packit 8f70b4
   Return true (preserving errno) if successful, false (setting errno)
Packit 8f70b4
   otherwise.  */
Packit 8f70b4
static bool
Packit 8f70b4
revert_tz (timezone_t tz)
Packit 8f70b4
{
Packit 8f70b4
  if (tz == local_tz)
Packit 8f70b4
    return true;
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      int saved_errno = errno;
Packit 8f70b4
      bool ok = change_env (tz);
Packit 8f70b4
      if (!ok)
Packit 8f70b4
        saved_errno = errno;
Packit 8f70b4
      tzfree (tz);
Packit 8f70b4
      errno = saved_errno;
Packit 8f70b4
      return ok;
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Use time zone TZ to compute localtime_r (T, TM).  */
Packit 8f70b4
struct tm *
Packit 8f70b4
localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
Packit 8f70b4
{
Packit 8f70b4
#ifdef HAVE_LOCALTIME_INFLOOP_BUG
Packit 8f70b4
  /* The -67768038400665599 comes from:
Packit 8f70b4
     https://lists.gnu.org/r/bug-gnulib/2017-07/msg00142.html
Packit 8f70b4
     On affected platforms the greatest POSIX-compatible time_t value
Packit 8f70b4
     that could return nonnull is 67768036191766798 (when
Packit 8f70b4
     TZ="XXX24:59:59" it resolves to the year 2**31 - 1 + 1900, on
Packit 8f70b4
     12-31 at 23:59:59), so test for that too while we're in the
Packit 8f70b4
     neighborhood.  */
Packit 8f70b4
  if (! (-67768038400665599 <= *t && *t <= 67768036191766798))
Packit 8f70b4
    {
Packit 8f70b4
      errno = EOVERFLOW;
Packit 8f70b4
      return NULL;
Packit 8f70b4
    }
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  if (!tz)
Packit 8f70b4
    return gmtime_r (t, tm);
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      timezone_t old_tz = set_tz (tz);
Packit 8f70b4
      if (old_tz)
Packit 8f70b4
        {
Packit 8f70b4
          bool abbr_saved = localtime_r (t, tm) && save_abbr (tz, tm);
Packit 8f70b4
          if (revert_tz (old_tz) && abbr_saved)
Packit 8f70b4
            return tm;
Packit 8f70b4
        }
Packit 8f70b4
      return NULL;
Packit 8f70b4
    }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Use time zone TZ to compute mktime (TM).  */
Packit 8f70b4
time_t
Packit 8f70b4
mktime_z (timezone_t tz, struct tm *tm)
Packit 8f70b4
{
Packit 8f70b4
  if (!tz)
Packit 8f70b4
    return timegm (tm);
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      timezone_t old_tz = set_tz (tz);
Packit 8f70b4
      if (old_tz)
Packit 8f70b4
        {
Packit 8f70b4
          time_t t = mktime (tm);
Packit 8f70b4
#if HAVE_TM_ZONE || HAVE_TZNAME
Packit 8f70b4
          time_t badtime = -1;
Packit 8f70b4
          struct tm tm_1;
Packit 8f70b4
          if ((t != badtime
Packit 8f70b4
               || (localtime_r (&t, &tm_1) && equal_tm (tm, &tm_1)))
Packit 8f70b4
              && !save_abbr (tz, tm))
Packit 8f70b4
            t = badtime;
Packit 8f70b4
#endif
Packit 8f70b4
          if (revert_tz (old_tz))
Packit 8f70b4
            return t;
Packit 8f70b4
        }
Packit 8f70b4
      return -1;
Packit 8f70b4
    }
Packit 8f70b4
}