Blame lib/time_rz.c

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