]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix putenv race conditions with undefined behavior.
authorPaul Eggert <eggert@cs.ucla.edu>
Sun, 12 Oct 2014 06:09:50 +0000 (23:09 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 12 Oct 2014 06:09:50 +0000 (23:09 -0700)
Do all putenv calls before Emacs creates any threads.
Use a safer way to modify the TZ environment variable in the
presence of multiple threads.  For further thread-safety,
prefer localtime_r and gmtime_r to localtime and gmtime,
and prefer struct tm's tm_gmtoff (if available) to calling
both localtime_r and gmtime_r.
* configure.ac (LOCALTIME_CACHE): Remove.
We needn't worry about SunOS 4 any more; Sun dropped support in 2003.
All uses of LOCALTIME_CACHE removed.  This simplifies the fix.
(tzalloc): Add check for this function.
* admin/merge-gnulib (GNULIB_MODULES): Add time_r, since Emacs now
calls localtime_r and gmtime_r directly.
* src/dbusbind.c (Fdbus__init_bus): Move xputenv call from here ...
(init_dbusbind): ... to this new function.
* src/emacs.c (main) [HAVE_DBUS]: Call it before creating threads.
* src/xterm.c (x_term_init): Move xputenv call from here ...
(init_xterm): ... to this new function.
* src/emacs.c (main) [USE_GTK]: Call it before creating threads.
* src/editfns.c (HAVE_TM_GMTOFF): Default to false.
(dump_tz_string): New constant.
(init_editfns): Use it.  This centralizes the dump_tz stuff.
Call set_time_zone_rule here, so that its xputenv is done
before Emacs goes multithreaded.
(mktime_z) [!HAVE_TZALLOC]: New function, which is typically
thread-safe enough for Emacs.
(format_time_string, Fdecode_time, Fcurrent_time_string)
(Fcurrent_time_zone):
Prefer localtime_r and gmtime_r, which are more thread-safe, to
localtime and gmtime.  Remove now-unnecessary calls to block_input.
(tm_gmtoff): New static function.
(Fdecode_time, Fcurrent_time_zone): Use it.
(Fencode_time): Use mktime_z, for better thread-safety.
(set_time_zone_rule): Now static.  Rewrite to be mostly thread-safe,
i.e., not quite thread-safe but good enough for Emacs typical usage.
Do not reclaim storage that is in the environment; let it leak.
Always call tzset, since localtime_r does not.
* src/emacs.c (dump_tz, Fdump_emacs) [HAVE_TZSET]: Remove dump_tz stuff.
This is now done in init_editfns.
* src/systime.h (mktime_z, timezone_t, tzalloc, tzfree) [!HAVE_TZALLOC]:
New macros and declarations, for platforms lacking tzalloc & friends.

Fixes: debbugs:8705
12 files changed:
ChangeLog
admin/CPP-DEFINES
admin/ChangeLog
admin/merge-gnulib
configure.ac
src/ChangeLog
src/dbusbind.c
src/editfns.c
src/emacs.c
src/lisp.h
src/systime.h
src/xterm.c

index 3aedab62234b83bf891838165adc4036335dc12f..9c9b68c99d41fb7763df900488e01ea1710e9dc5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2014-10-12  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Fix putenv race conditions that can crash Emacs (Bug#8705).
+       * configure.ac (LOCALTIME_CACHE): Remove.
+       We needn't worry about SunOS 4 any more; Sun dropped support in 2003.
+       All uses of LOCALTIME_CACHE removed.  This simplifies the fix.
+       (tzalloc): Add check for this function.
+
 2014-10-06  Jan Djärv  <jan.h.d@swipnet.se>
 
        * configure.ac: Add -Wno-string-plus-int for clang.
index 066347b3826df0182d3597529d7f4d63530a8acf..a98d1b9039e72289618f208ab3906fb66884b5ab 100644 (file)
@@ -368,7 +368,6 @@ HAVE___FPENDING
 INTERNAL_TERMINAL
 IS_ANY_SEP
 IS_DIRECTORY_SEP
-LOCALTIME_CACHE
 MAIL_USE_FLOCK
 MAIL_USE_LOCKF
 MAIL_USE_POP
index 742a64ce15d24e70e0ce1dbbc3fd02c562260255..258ce04783fdc4ca04337a1c1a99a6cb22111768 100644 (file)
@@ -1,3 +1,9 @@
+2014-10-12  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Fix putenv race conditions with undefined behavior (Bug#8705).
+       * merge-gnulib (GNULIB_MODULES): Add time_r, since Emacs now
+       calls localtime_r and gmtime_r directly.
+
 2014-10-07  Glenn Morris  <rgm@gnu.org>
 
        * unidata/Makefile.in: Check for deleted uni- files.  (Bug#18489)
index 5b9b716bed22b6df48bf2e3a052956e574339903..dc3cd6cf5413da71bc1fd29925c0123db57c27fb 100755 (executable)
@@ -37,7 +37,7 @@ GNULIB_MODULES='
   pipe2 pselect pthread_sigmask putenv qacl readlink readlinkat
   sig2str socklen stat-time stdalign stdio
   strftime strtoimax strtoumax symlink sys_stat
-  sys_time time timer-time timespec-add timespec-sub
+  sys_time time time_r timer-time timespec-add timespec-sub
   unsetenv update-copyright utimens
   vla warnings
 '
index 6d047a6234a9b636d7f3372e49ab42d77b211b3c..d94700018b138634ff85321d74ce4e451bf192c2 100644 (file)
@@ -3915,43 +3915,7 @@ AC_SUBST(KRB4LIB)
 
 AC_CHECK_HEADERS(valgrind/valgrind.h)
 
-AC_CHECK_FUNCS_ONCE(tzset)
-AC_MSG_CHECKING(whether localtime caches TZ)
-AC_CACHE_VAL(emacs_cv_localtime_cache,
-[if test x$ac_cv_func_tzset = xyes; then
-AC_RUN_IFELSE([AC_LANG_SOURCE([[#include <time.h>
-char TZ_GMT0[] = "TZ=GMT0";
-char TZ_PST8[] = "TZ=PST8";
-main()
-{
-  time_t now = time ((time_t *) 0);
-  int hour_GMT0, hour_unset;
-  if (putenv (TZ_GMT0) != 0)
-    exit (1);
-  hour_GMT0 = localtime (&now)->tm_hour;
-  unsetenv("TZ");
-  hour_unset = localtime (&now)->tm_hour;
-  if (putenv (TZ_PST8) != 0)
-    exit (1);
-  if (localtime (&now)->tm_hour == hour_GMT0)
-    exit (1);
-  unsetenv("TZ");
-  if (localtime (&now)->tm_hour != hour_unset)
-    exit (1);
-  exit (0);
-}]])], emacs_cv_localtime_cache=no, emacs_cv_localtime_cache=yes,
-[# If we have tzset, assume the worst when cross-compiling.
-emacs_cv_localtime_cache=yes])
-else
-       # If we lack tzset, report that localtime does not cache TZ,
-       # since we can't invalidate the cache if we don't have tzset.
-       emacs_cv_localtime_cache=no
-fi])dnl
-AC_MSG_RESULT($emacs_cv_localtime_cache)
-if test $emacs_cv_localtime_cache = yes; then
-  AC_DEFINE(LOCALTIME_CACHE, 1,
-           [Define to 1 if localtime caches TZ.])
-fi
+AC_CHECK_FUNCS_ONCE(tzalloc tzset)
 
 ok_so_far=yes
 AC_CHECK_FUNC(socket, , ok_so_far=no)
index f511aa19a19720d04997f8b6d46ea6ae46a810f5..568022f478bbe89507e51c490e01b8bf33f80933 100644 (file)
@@ -1,3 +1,41 @@
+2014-10-12  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Fix putenv race conditions with undefined behavior (Bug#8705).
+       Do all putenv calls before Emacs creates any threads.
+       Use a safer way to modify the TZ environment variable in the
+       presence of multiple threads.  For further thread-safety,
+       prefer localtime_r and gmtime_r to localtime and gmtime,
+       and prefer struct tm's tm_gmtoff (if available) to calling
+       both localtime_r and gmtime_r.
+       * dbusbind.c (Fdbus__init_bus): Move xputenv call from here ...
+       (init_dbusbind): ... to this new function.
+       * emacs.c (main) [HAVE_DBUS]: Call it before creating threads.
+       * xterm.c (x_term_init): Move xputenv call from here ...
+       (init_xterm): ... to this new function.
+       * emacs.c (main) [USE_GTK]: Call it before creating threads.
+       * editfns.c (HAVE_TM_GMTOFF): Default to false.
+       (dump_tz_string): New constant.
+       (init_editfns): Use it.  This centralizes the dump_tz stuff.
+       Call set_time_zone_rule here, so that its xputenv is done
+       before Emacs goes multithreaded.
+       (mktime_z) [!HAVE_TZALLOC]: New function, which is typically
+       thread-safe enough for Emacs.
+       (format_time_string, Fdecode_time, Fcurrent_time_string)
+       (Fcurrent_time_zone):
+       Prefer localtime_r and gmtime_r, which are more thread-safe, to
+       localtime and gmtime.  Remove now-unnecessary calls to block_input.
+       (tm_gmtoff): New static function.
+       (Fdecode_time, Fcurrent_time_zone): Use it.
+       (Fencode_time): Use mktime_z, for better thread-safety.
+       (set_time_zone_rule): Now static.  Rewrite to be mostly thread-safe,
+       i.e., not quite thread-safe but good enough for Emacs typical usage.
+       Do not reclaim storage that is in the environment; let it leak.
+       Always call tzset, since localtime_r does not.
+       * emacs.c (dump_tz, Fdump_emacs) [HAVE_TZSET]: Remove dump_tz stuff.
+       This is now done in init_editfns.
+       * systime.h (mktime_z, timezone_t, tzalloc, tzfree) [!HAVE_TZALLOC]:
+       New macros and declarations, for platforms lacking tzalloc & friends.
+
 2014-10-09  Paul Eggert  <eggert@cs.ucla.edu>
 
        * lisp.h (USE_STACK_STRING): Now true only if USE_STACK CONS.
index f81666ba7bd42c107995194b7ea7c2905357855f..4852739d8e4405339b860e4deeb68ec8ea708ecb 100644 (file)
@@ -1054,6 +1054,7 @@ xd_remove_watch (DBusWatch *watch, void *data)
 
   /* Unset session environment.  */
 #if 0
+  /* This is buggy, since unsetenv is not thread-safe.  */
   if (XSYMBOL (QCdbus_session_bus) == data)
     {
       XD_DEBUG_MESSAGE ("unsetenv DBUS_SESSION_BUS_ADDRESS");
@@ -1219,9 +1220,6 @@ this connection to those buses.  */)
       XSETFASTINT (val, (intptr_t) connection);
       xd_registered_buses = Fcons (Fcons (bus, val), xd_registered_buses);
 
-      /* We do not want to abort.  */
-      xputenv ("DBUS_FATAL_WARNINGS=0");
-
       /* Cleanup.  */
       dbus_error_free (&derror);
     }
@@ -1737,6 +1735,13 @@ xd_read_queued_messages (int fd, void *data)
 }
 
 \f
+void
+init_dbusbind (void)
+{
+  /* We do not want to abort.  */
+  xputenv ("DBUS_FATAL_WARNINGS=0");
+}
+
 void
 syms_of_dbusbind (void)
 {
index e7c960dfffe22b75ae141c7c48acd3d44d51e392..d17d809c9eccec93cdab1f8155cbef16dc82235c 100644 (file)
@@ -64,11 +64,17 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 extern Lisp_Object w32_get_internal_run_time (void);
 #endif
 
+static void set_time_zone_rule (char const *);
 static Lisp_Object format_time_string (char const *, ptrdiff_t, struct timespec,
                                       bool, struct tm *);
+static long int tm_gmtoff (struct tm *);
 static int tm_diff (struct tm *, struct tm *);
 static void update_buffer_properties (ptrdiff_t, ptrdiff_t);
 
+#ifndef HAVE_TM_GMTOFF
+# define HAVE_TM_GMTOFF false
+#endif
+
 static Lisp_Object Qbuffer_access_fontify_functions;
 
 /* Symbol for the text property used to mark fields.  */
@@ -79,15 +85,12 @@ Lisp_Object Qfield;
 
 static Lisp_Object Qboundary;
 
-/* The startup value of the TZ environment variable so it can be
-   restored if the user calls set-time-zone-rule with a nil
-   argument.  If null, the TZ environment variable was unset.  */
+/* The startup value of the TZ environment variable; null if unset.  */
 static char const *initial_tz;
 
-/* True if the static variable tzvalbuf (defined in
-   set_time_zone_rule) is part of 'environ'.  */
-static bool tzvalbuf_in_environ;
-
+/* A valid but unlikely setting for the TZ environment variable.
+   It is OK (though a bit slower) if the user chooses this value.  */
+static char const dump_tz_string[] = "TZ=UtC0";
 
 void
 init_editfns (void)
@@ -101,13 +104,38 @@ init_editfns (void)
   init_system_name ();
 
 #ifndef CANNOT_DUMP
-  /* Don't bother with this on initial start when just dumping out */
+  /* When just dumping out, set the time zone to a known unlikely value
+     and skip the rest of this function.  */
   if (!initialized)
-    return;
-#endif /* not CANNOT_DUMP */
+    {
+# ifdef HAVE_TZSET
+      xputenv ((char *) dump_tz_string);
+      tzset ();
+# endif
+      return;
+    }
+#endif
+
+  char *tz = getenv ("TZ");
+  initial_tz = tz;
 
-  initial_tz = getenv ("TZ");
-  tzvalbuf_in_environ = 0;
+#if !defined CANNOT_DUMP && defined HAVE_TZSET
+  /* If the execution TZ happens to be the same as the dump TZ,
+     change it to some other value and then change it back,
+     to force the underlying implementation to reload the TZ info.
+     This is needed on implementations that load TZ info from files,
+     since the TZ file contents may differ between dump and execution.  */
+  if (tz && strcmp (tz, &dump_tz_string[sizeof "TZ=" - 1]) == 0)
+    {
+      ++*tz;
+      tzset ();
+      --*tz;
+    }
+#endif
+
+  /* Call set_time_zone_rule now, so that its call to putenv is done
+     before multiple threads are active.  */
+  set_time_zone_rule (tz);
 
   pw = getpwuid (getuid ());
 #ifdef MSDOS
@@ -1373,6 +1401,30 @@ time_overflow (void)
   error ("Specified time is not representable");
 }
 
+/* A substitute for mktime_z on platforms that lack it.  It's not
+   thread-safe, but should be good enough for Emacs in typical use.  */
+#ifndef HAVE_TZALLOC
+time_t
+mktime_z (timezone_t tz, struct tm *tm)
+{
+  char *oldtz = getenv ("TZ");
+  USE_SAFE_ALLOCA;
+  if (oldtz)
+    {
+      size_t oldtzsize = strlen (oldtz) + 1;
+      char *oldtzcopy = SAFE_ALLOCA (oldtzsize);
+      oldtz = strcpy (oldtzcopy, oldtz);
+    }
+  block_input ();
+  set_time_zone_rule (tz);
+  time_t t = mktime (tm);
+  set_time_zone_rule (oldtz);
+  unblock_input ();
+  SAFE_FREE ();
+  return t;
+}
+#endif
+
 /* Return the upper part of the time T (everything but the bottom 16 bits).  */
 static EMACS_INT
 hi_time (time_t t)
@@ -1768,39 +1820,28 @@ format_time_string (char const *format, ptrdiff_t formatlen,
   size_t len;
   Lisp_Object bufstring;
   int ns = t.tv_nsec;
-  struct tm *tm;
   USE_SAFE_ALLOCA;
 
-  while (1)
-    {
-      time_t *taddr = &t.tv_sec;
-      block_input ();
-
-      synchronize_system_time_locale ();
-
-      tm = ut ? gmtime (taddr) : localtime (taddr);
-      if (! tm)
-       {
-         unblock_input ();
-         time_overflow ();
-       }
-      *tmp = *tm;
+  tmp = ut ? gmtime_r (&t.tv_sec, tmp) : localtime_r (&t.tv_sec, tmp);
+  if (! tmp)
+    time_overflow ();
+  synchronize_system_time_locale ();
 
+  while (true)
+    {
       buf[0] = '\1';
-      len = emacs_nmemftime (buf, size, format, formatlen, tm, ut, ns);
+      len = emacs_nmemftime (buf, size, format, formatlen, tmp, ut, ns);
       if ((0 < len && len < size) || (len == 0 && buf[0] == '\0'))
        break;
 
       /* Buffer was too small, so make it bigger and try again.  */
-      len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tm, ut, ns);
-      unblock_input ();
+      len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tmp, ut, ns);
       if (STRING_BYTES_BOUND <= len)
        string_overflow ();
       size = len + 1;
       buf = SAFE_ALLOCA (size);
     }
 
-  unblock_input ();
   bufstring = make_unibyte_string (buf, len);
   SAFE_FREE ();
   return code_convert_string_norecord (bufstring, Vlocale_coding_system, 0);
@@ -1824,38 +1865,30 @@ DOW and ZONE.)  */)
   (Lisp_Object specified_time)
 {
   time_t time_spec = lisp_seconds_argument (specified_time);
-  struct tm save_tm;
-  struct tm *decoded_time;
-  Lisp_Object list_args[9];
+  struct tm local_tm, gmt_tm;
 
-  block_input ();
-  decoded_time = localtime (&time_spec);
-  if (decoded_time)
-    save_tm = *decoded_time;
-  unblock_input ();
-  if (! (decoded_time
-        && MOST_NEGATIVE_FIXNUM - TM_YEAR_BASE <= save_tm.tm_year
-        && save_tm.tm_year <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE))
+  if (! (localtime_r (&time_spec, &local_tm)
+        && MOST_NEGATIVE_FIXNUM - TM_YEAR_BASE <= local_tm.tm_year
+        && local_tm.tm_year <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE))
     time_overflow ();
-  XSETFASTINT (list_args[0], save_tm.tm_sec);
-  XSETFASTINT (list_args[1], save_tm.tm_min);
-  XSETFASTINT (list_args[2], save_tm.tm_hour);
-  XSETFASTINT (list_args[3], save_tm.tm_mday);
-  XSETFASTINT (list_args[4], save_tm.tm_mon + 1);
-  /* On 64-bit machines an int is narrower than EMACS_INT, thus the
-     cast below avoids overflow in int arithmetics.  */
-  XSETINT (list_args[5], TM_YEAR_BASE + (EMACS_INT) save_tm.tm_year);
-  XSETFASTINT (list_args[6], save_tm.tm_wday);
-  list_args[7] = save_tm.tm_isdst ? Qt : Qnil;
 
-  block_input ();
-  decoded_time = gmtime (&time_spec);
-  if (decoded_time == 0)
-    list_args[8] = Qnil;
-  else
-    XSETINT (list_args[8], tm_diff (&save_tm, decoded_time));
-  unblock_input ();
-  return Flist (9, list_args);
+  /* Avoid overflow when INT_MAX < EMACS_INT_MAX.  */
+  EMACS_INT tm_year_base = TM_YEAR_BASE;
+
+  return Flist (9, ((Lisp_Object [])
+                   {make_number (local_tm.tm_sec),
+                    make_number (local_tm.tm_min),
+                    make_number (local_tm.tm_hour),
+                    make_number (local_tm.tm_mday),
+                    make_number (local_tm.tm_mon + 1),
+                    make_number (local_tm.tm_year + tm_year_base),
+                    make_number (local_tm.tm_wday),
+                    local_tm.tm_isdst ? Qt : Qnil,
+                    (HAVE_TM_GMTOFF
+                     ? make_number (tm_gmtoff (&local_tm))
+                     : gmtime_r (&time_spec, &gmt_tm)
+                     ? make_number (tm_diff (&local_tm, &gmt_tm))
+                     : Qnil)}));
 }
 
 /* Return OBJ - OFFSET, checking that OBJ is a valid fixnum and that
@@ -1911,18 +1944,12 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
   if (CONSP (zone))
     zone = XCAR (zone);
   if (NILP (zone))
-    {
-      block_input ();
-      value = mktime (&tm);
-      unblock_input ();
-    }
+    value = mktime (&tm);
   else
     {
       static char const tzbuf_format[] = "XXX%s%"pI"d:%02d:%02d";
       char tzbuf[sizeof tzbuf_format + INT_STRLEN_BOUND (EMACS_INT)];
-      char *old_tzstring;
       const char *tzstring;
-      USE_SAFE_ALLOCA;
 
       if (EQ (zone, Qt))
        tzstring = "UTC0";
@@ -1939,29 +1966,13 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
          tzstring = tzbuf;
        }
       else
-       error ("Invalid time zone specification");
+       tzstring = 0;
 
-      old_tzstring = getenv ("TZ");
-      if (old_tzstring)
-       {
-         char *buf = SAFE_ALLOCA (strlen (old_tzstring) + 1);
-         old_tzstring = strcpy (buf, old_tzstring);
-       }
-
-      block_input ();
-
-      /* Set TZ before calling mktime; merely adjusting mktime's returned
-        value doesn't suffice, since that would mishandle leap seconds.  */
-      set_time_zone_rule (tzstring);
-
-      value = mktime (&tm);
-
-      set_time_zone_rule (old_tzstring);
-#ifdef LOCALTIME_CACHE
-      tzset ();
-#endif
-      unblock_input ();
-      SAFE_FREE ();
+      timezone_t tz = tzstring ? tzalloc (tzstring) : 0;
+      if (! tz)
+       error ("Invalid time zone specification");
+      value = mktime_z (tz, &tm);
+      tzfree (tz);
     }
 
   if (value == (time_t) -1)
@@ -1987,34 +1998,27 @@ but this is considered obsolete.  */)
   (Lisp_Object specified_time)
 {
   time_t value = lisp_seconds_argument (specified_time);
-  struct tm *tm;
-  char buf[sizeof "Mon Apr 30 12:49:17 " + INT_STRLEN_BOUND (int) + 1];
-  int len IF_LINT (= 0);
 
   /* Convert to a string in ctime format, except without the trailing
      newline, and without the 4-digit year limit.  Don't use asctime
      or ctime, as they might dump core if the year is outside the
      range -999 .. 9999.  */
-  block_input ();
-  tm = localtime (&value);
-  if (tm)
-    {
-      static char const wday_name[][4] =
-       { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-      static char const mon_name[][4] =
-       { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-      printmax_t year_base = TM_YEAR_BASE;
-
-      len = sprintf (buf, "%s %s%3d %02d:%02d:%02d %"pMd,
-                    wday_name[tm->tm_wday], mon_name[tm->tm_mon], tm->tm_mday,
-                    tm->tm_hour, tm->tm_min, tm->tm_sec,
-                    tm->tm_year + year_base);
-    }
-  unblock_input ();
-  if (! tm)
+  struct tm tm;
+  if (! localtime_r (&value, &tm))
     time_overflow ();
 
+  static char const wday_name[][4] =
+    { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+  static char const mon_name[][4] =
+    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+  printmax_t year_base = TM_YEAR_BASE;
+  char buf[sizeof "Mon Apr 30 12:49:17 " + INT_STRLEN_BOUND (int) + 1];
+  int len = sprintf (buf, "%s %s%3d %02d:%02d:%02d %"pMd,
+                    wday_name[tm.tm_wday], mon_name[tm.tm_mon], tm.tm_mday,
+                    tm.tm_hour, tm.tm_min, tm.tm_sec,
+                    tm.tm_year + year_base);
+
   return make_unibyte_string (buf, len);
 }
 
@@ -2041,6 +2045,17 @@ tm_diff (struct tm *a, struct tm *b)
          + (a->tm_sec - b->tm_sec));
 }
 
+/* Yield A's UTC offset, or an unspecified value if unknown.  */
+static long int
+tm_gmtoff (struct tm *a)
+{
+#if HAVE_TM_GMTOFF
+  return a->tm_gmtoff;
+#else
+  return 0;
+#endif
+}
+
 DEFUN ("current-time-zone", Fcurrent_time_zone, Scurrent_time_zone, 0, 1, 0,
        doc: /* Return the offset and name for the local time zone.
 This returns a list of the form (OFFSET NAME).
@@ -2059,32 +2074,30 @@ the data it can't find.  */)
   (Lisp_Object specified_time)
 {
   struct timespec value;
-  int offset;
-  struct tm *t;
-  struct tm localtm;
+  struct tm local_tm, gmt_tm;
   Lisp_Object zone_offset, zone_name;
 
   zone_offset = Qnil;
   value = make_timespec (lisp_seconds_argument (specified_time), 0);
-  zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value, 0, &localtm);
-  block_input ();
-  t = gmtime (&value.tv_sec);
-  if (t)
-    offset = tm_diff (&localtm, t);
-  unblock_input ();
+  zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value, 0, &local_tm);
 
-  if (t)
+  if (HAVE_TM_GMTOFF || gmtime_r (&value.tv_sec, &gmt_tm))
     {
+      long int offset = (HAVE_TM_GMTOFF
+                        ? tm_gmtoff (&local_tm)
+                        : tm_diff (&local_tm, &gmt_tm));
       zone_offset = make_number (offset);
       if (SCHARS (zone_name) == 0)
        {
          /* No local time zone name is available; use "+-NNNN" instead.  */
-         int m = offset / 60;
-         int am = offset < 0 ? - m : m;
-         char buf[sizeof "+00" + INT_STRLEN_BOUND (int)];
-         zone_name = make_formatted_string (buf, "%c%02d%02d",
+         long int m = offset / 60;
+         long int am = offset < 0 ? - m : m;
+         long int hour = am / 60;
+         int min = am % 60;
+         char buf[sizeof "+00" + INT_STRLEN_BOUND (long int)];
+         zone_name = make_formatted_string (buf, "%c%02ld%02d",
                                             (offset < 0 ? '-' : '+'),
-                                            am / 60, am % 60);
+                                            hour, min);
        }
     }
 
@@ -2123,12 +2136,12 @@ only the former.  */)
 
 /* Set the local time zone rule to TZSTRING.
 
-   This function is not thread-safe, partly because putenv, unsetenv
-   and tzset are not, and partly because of the static storage it
-   updates.  Other threads that invoke localtime etc. may be adversely
-   affected while this function is executing.  */
+   This function is not thread-safe, in theory because putenv is not,
+   but mostly because of the static storage it updates.  Other threads
+   that invoke localtime etc. may be adversely affected while this
+   function is executing.  */
 
-void
+static void
 set_time_zone_rule (const char *tzstring)
 {
   /* A buffer holding a string of the form "TZ=value", intended
@@ -2137,75 +2150,47 @@ set_time_zone_rule (const char *tzstring)
   static ptrdiff_t tzvalbufsize;
 
   int tzeqlen = sizeof "TZ=" - 1;
+  ptrdiff_t tzstringlen = tzstring ? strlen (tzstring) : 0;
+  char *tzval = tzvalbuf;
+  bool new_tzvalbuf = tzvalbufsize <= tzeqlen + tzstringlen;
 
-#ifdef LOCALTIME_CACHE
-  /* These two values are known to load tz files in buggy implementations,
-     i.e., Solaris 1 executables running under either Solaris 1 or Solaris 2.
-     Their values shouldn't matter in non-buggy implementations.
-     We don't use string literals for these strings,
-     since if a string in the environment is in readonly
-     storage, it runs afoul of bugs in SVR4 and Solaris 2.3.
-     See Sun bugs 1113095 and 1114114, ``Timezone routines
-     improperly modify environment''.  */
-
-  static char set_time_zone_rule_tz[][sizeof "TZ=GMT+0"]
-    = { "TZ=GMT+0", "TZ=GMT+1" };
-
-  /* In SunOS 4.1.3_U1 and 4.1.4, if TZ has a value like
-     "US/Pacific" that loads a tz file, then changes to a value like
-     "XXX0" that does not load a tz file, and then changes back to
-     its original value, the last change is (incorrectly) ignored.
-     Also, if TZ changes twice in succession to values that do
-     not load a tz file, tzset can dump core (see Sun bug#1225179).
-     The following code works around these bugs.  */
+  if (new_tzvalbuf)
+    {
+      /* Do not attempt to free the old tzvalbuf, since another thread
+        may be using it.  In practice, the first allocation is large
+        enough and memory does not leak.  */
+      tzval = xpalloc (NULL, &tzvalbufsize,
+                      tzeqlen + tzstringlen - tzvalbufsize + 1, -1, 1);
+      tzvalbuf = tzval;
+      tzval[1] = 'Z';
+      tzval[2] = '=';
+    }
 
   if (tzstring)
     {
-      /* Temporarily set TZ to a value that loads a tz file
-        and that differs from tzstring.  */
-      bool eq0 = strcmp (tzstring, set_time_zone_rule_tz[0] + tzeqlen) == 0;
-      xputenv (set_time_zone_rule_tz[eq0]);
+      /* Modify TZVAL in place.  Although this is dicey in a
+        multithreaded environment, we know of no portable alternative.
+        Calling putenv or setenv could crash some other thread.  */
+      tzval[0] = 'T';
+      strcpy (tzval + tzeqlen, tzstring);
     }
   else
     {
-      /* The implied tzstring is unknown, so temporarily set TZ to
-        two different values that each load a tz file.  */
-      xputenv (set_time_zone_rule_tz[0]);
-      tzset ();
-      xputenv (set_time_zone_rule_tz[1]);
+      /* Turn 'TZ=whatever' into an empty environment variable 'tZ='.
+        Although this is also dicey, calling unsetenv here can crash Emacs.
+        See Bug#8705.  */
+      tzval[0] = 't';
+      tzval[tzeqlen] = 0;
     }
-  tzset ();
-  tzvalbuf_in_environ = 0;
-#endif
 
-  if (!tzstring)
-    {
-      unsetenv ("TZ");
-      tzvalbuf_in_environ = 0;
-    }
-  else
+  if (new_tzvalbuf)
     {
-      ptrdiff_t tzstringlen = strlen (tzstring);
-
-      if (tzvalbufsize <= tzeqlen + tzstringlen)
-       {
-         unsetenv ("TZ");
-         tzvalbuf_in_environ = 0;
-         tzvalbuf = xpalloc (tzvalbuf, &tzvalbufsize,
-                             tzeqlen + tzstringlen - tzvalbufsize + 1, -1, 1);
-         memcpy (tzvalbuf, "TZ=", tzeqlen);
-       }
-
-      strcpy (tzvalbuf + tzeqlen, tzstring);
-
-      if (!tzvalbuf_in_environ)
-       {
-         xputenv (tzvalbuf);
-         tzvalbuf_in_environ = 1;
-       }
+      /* Although this is not thread-safe, in practice this runs only
+        on startup when there is only one thread.  */
+      xputenv (tzval);
     }
 
-#ifdef LOCALTIME_CACHE
+#ifdef HAVE_TZSET
   tzset ();
 #endif
 }
index 60b67b5a902c78c46c1ce5e5597404a7de086290..90182e53e700c93274da45ed1cff88e15b94cb35 100644 (file)
@@ -578,12 +578,6 @@ DEFUN ("invocation-directory", Finvocation_directory, Sinvocation_directory,
 }
 
 \f
-#ifdef HAVE_TZSET
-/* A valid but unlikely value for the TZ environment value.
-   It is OK (though a bit slower) if the user actually chooses this value.  */
-static char const dump_tz[] = "UtC0";
-#endif
-
 /* Test whether the next argument in ARGV matches SSTR or a prefix of
    LSTR (at least MINLEN characters).  If so, then if VALPTR is non-null
    (the argument is supposed to have a value) store in *VALPTR either
@@ -1548,8 +1542,23 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 
   init_charset ();
 
-  init_editfns (); /* init_process_emacs uses Voperating_system_release. */
-  init_process_emacs (); /* init_display uses add_keyboard_wait_descriptor. */
+  /* This calls putenv and so must precede init_process_emacs.  Also,
+     it sets Voperating_system_release, which init_process_emacs uses.  */
+  init_editfns ();
+
+  /* These two call putenv.  */
+#ifdef HAVE_DBUS
+  init_dbusbind ();
+#endif
+#ifdef USE_GTK
+  init_xterm ();
+#endif
+
+  /* This can create a thread that may call getenv, so it must follow
+     all calls to putenv and setenv.  Also, this sets up
+     add_keyboard_wait_descriptor, which init_display uses.  */
+  init_process_emacs ();
+
   init_keyboard ();    /* This too must precede init_sys_modes.  */
   if (!noninteractive)
     init_display ();   /* Determine terminal type.  Calls init_sys_modes.  */
@@ -1586,26 +1595,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
                            build_string ("loadup.el"));
     }
 
-  if (initialized)
-    {
-#ifdef HAVE_TZSET
-      {
-       /* If the execution TZ happens to be the same as the dump TZ,
-          change it to some other value and then change it back,
-          to force the underlying implementation to reload the TZ info.
-          This is needed on implementations that load TZ info from files,
-          since the TZ file contents may differ between dump and execution.  */
-       char *tz = getenv ("TZ");
-       if (tz && !strcmp (tz, dump_tz))
-         {
-           ++*tz;
-           tzset ();
-           --*tz;
-         }
-      }
-#endif
-    }
-
   /* Set up for profiling.  This is known to work on FreeBSD,
      GNU/Linux and MinGW.  It might work on some other systems too.
      Give it a try and tell us if it works on your system.  To compile
@@ -1630,15 +1619,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 
   initialized = 1;
 
-#ifdef LOCALTIME_CACHE
-  /* Some versions of localtime have a bug.  They cache the value of the time
-     zone rather than looking it up every time.  Since localtime() is
-     called to bolt the undumping time into the undumped emacs, this
-     results in localtime ignoring the TZ environment variable.
-     This flushes the new TZ value into localtime.  */
-  tzset ();
-#endif /* defined (LOCALTIME_CACHE) */
-
   /* Enter editor command loop.  This never returns.  */
   Frecursive_edit ();
   /* NOTREACHED */
@@ -2119,14 +2099,6 @@ You must run Emacs in batch mode in order to dump it.  */)
   tem = Vpurify_flag;
   Vpurify_flag = Qnil;
 
-#ifdef HAVE_TZSET
-  set_time_zone_rule (dump_tz);
-#ifndef LOCALTIME_CACHE
-  /* Force a tz reload, since set_time_zone_rule doesn't.  */
-  tzset ();
-#endif
-#endif
-
   fflush (stdout);
   /* Tell malloc where start of impure now is.  */
   /* Also arrange for warnings when nearly out of space.  */
index 9e4cc5fdc53aa5fa7326ac05b9acf2d7f4410ab3..89f29ea268b6c68492200b7af716f2dc72935661 100644 (file)
@@ -3990,7 +3990,6 @@ extern Lisp_Object make_buffer_string_both (ptrdiff_t, ptrdiff_t, ptrdiff_t,
                                            ptrdiff_t, bool);
 extern void init_editfns (void);
 extern void syms_of_editfns (void);
-extern void set_time_zone_rule (const char *);
 
 /* Defined in buffer.c.  */
 extern bool mouse_face_overlay_overlaps (Lisp_Object);
@@ -4398,6 +4397,7 @@ extern void syms_of_xsmfns (void);
 extern void syms_of_xselect (void);
 
 /* Defined in xterm.c.  */
+extern void init_xterm (void);
 extern void syms_of_xterm (void);
 #endif /* HAVE_X_WINDOWS */
 
@@ -4419,6 +4419,7 @@ extern void syms_of_decompress (void);
 
 #ifdef HAVE_DBUS
 /* Defined in dbusbind.c.  */
+void init_dbusbind (void);
 void syms_of_dbusbind (void);
 #endif
 
index a834bce76dca240bfc13a2d552d3c719e666511b..8f018044660984f475afe4aba1aa8901a6428aa5 100644 (file)
@@ -93,6 +93,22 @@ extern bool decode_time_components (Lisp_Object, Lisp_Object, Lisp_Object,
 extern struct timespec lisp_time_argument (Lisp_Object);
 #endif
 
+#ifndef HAVE_TZALLOC
+# undef mktime_z
+# undef timezone_t
+# undef tzalloc
+# undef tzfree
+# define mktime_z emacs_mktime_z
+# define timezone_t emacs_timezone_t
+# define tzalloc emacs_tzalloc
+# define tzfree emacs_tzfree
+typedef char const *timezone_t;
+INLINE timezone_t tzalloc (char const *name) { return name; }
+INLINE void tzfree (timezone_t tz) { }
+/* Defined in editfns.c.  */
+extern time_t mktime_z (timezone_t, struct tm *);
+#endif
+
 INLINE_HEADER_END
 
 #endif /* EMACS_SYSTIME_H */
index aff57f6a17ef951b86a58bd46be5684c7c4c9229..f32aea031f91181603c677a0915e840e01bba890 100644 (file)
@@ -10717,10 +10717,6 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name)
 
         XSetLocaleModifiers ("");
 
-        /* Emacs can only handle core input events, so make sure
-           Gtk doesn't use Xinput or Xinput2 extensions.  */
-       xputenv ("GDK_CORE_DEVICE_EVENTS=1");
-
         /* Work around GLib bug that outputs a faulty warning. See
            https://bugzilla.gnome.org/show_bug.cgi?id=563627.  */
         id = g_log_set_handler ("GLib", G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL
@@ -11470,6 +11466,15 @@ x_initialize (void)
   XSetIOErrorHandler (x_io_error_quitter);
 }
 
+#ifdef USE_GTK
+void
+init_xterm (void)
+{
+  /* Emacs can handle only core input events, so make sure
+     Gtk doesn't use Xinput or Xinput2 extensions.  */
+  xputenv ("GDK_CORE_DEVICE_EVENTS=1");
+}
+#endif
 
 void
 syms_of_xterm (void)