Blame lib/time_rz.c

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