Blame lib/time_rz.c

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