Fix tzalloc(nullptr) and add a test.

This works (by reading /etc/localtime) on NetBSD, but not on Android
since we have no such file. Fix that by using our equivalent system
property instead.

Also s/time zone/timezone/ in documentation and comments. We've always
been inconsistent about this (as is upstream in code comments and
documentation) but it seems especially odd now we expose a _type_ that
spells it "timezone" to talk of "time zone" even as we're describing
that type and its associated functions.

Bug: https://github.com/chronotope/chrono/issues/499
Test: treehugger
Change-Id: I142995a3ab4deff1073a0aa9e63ce8eac850b93d
diff --git a/libc/tzcode/bionic.cpp b/libc/tzcode/bionic.cpp
index d2b5d80..7091707 100644
--- a/libc/tzcode/bionic.cpp
+++ b/libc/tzcode/bionic.cpp
@@ -36,10 +36,45 @@
 #include "private/CachedProperty.h"
 
 extern "C" void tzset_unlocked(void);
+extern "C" void __bionic_get_system_tz(char* buf, size_t n);
 extern "C" int __bionic_open_tzdata(const char*, int32_t*);
 
 extern "C" void tzsetlcl(char const*);
 
+void __bionic_get_system_tz(char* buf, size_t n) {
+  static CachedProperty persist_sys_timezone("persist.sys.timezone");
+  const char* name = persist_sys_timezone.Get();
+
+  // If the system property is not set, perhaps because this is called
+  // before the default value has been set (the recovery image being a
+  // classic example), fall back to GMT.
+  if (name == nullptr) name = "GMT";
+
+  strlcpy(buf, name, n);
+
+  if (!strcmp(buf, "GMT")) {
+    // Typically we'll set the system property to an Olson ID, but
+    // java.util.TimeZone also supports the "GMT+xxxx" style, and at
+    // least historically (see http://b/25463955) some Android-based set
+    // top boxes would get the timezone from the TV network in this format
+    // and use it directly in the system property. This caused trouble
+    // for native code because POSIX and Java disagree about the sign in
+    // a timezone string. For POSIX, "GMT+3" means "3 hours west/behind",
+    // but for Java it means "3 hours east/ahead". Since (a) Java is the
+    // one that matches human expectations and (b) this system property is
+    // used directly by Java, we flip the sign here to translate from Java
+    // to POSIX. We only need to worry about the "GMT+xxxx" case because
+    // the expectation is that these are valid java.util.TimeZone ids,
+    // not general POSIX custom timezone specifications (which is why this
+    // code only applies to the system property, and not to the environment
+    // variable).
+    char sign = buf[3];
+    if (sign == '-' || sign == '+') {
+      buf[3] = (sign == '-') ? '+' : '-';
+    }
+  }
+}
+
 void tzset_unlocked() {
   // The TZ environment variable is meant to override the system-wide setting.
   const char* name = getenv("TZ");
@@ -47,26 +82,10 @@
 
   // If that's not set, look at the "persist.sys.timezone" system property.
   if (name == nullptr) {
-    static CachedProperty persist_sys_timezone("persist.sys.timezone");
-
-    if ((name = persist_sys_timezone.Get()) != nullptr && strlen(name) > 3) {
-      // POSIX and Java disagree about the sign in a timezone string. For POSIX, "GMT+3" means
-      // "3 hours west/behind", but for Java it means "3 hours east/ahead". Since (a) Java is
-      // the one that matches human expectations and (b) this system property is used directly
-      // by Java, we flip the sign here to translate from Java to POSIX. http://b/25463955.
-      char sign = name[3];
-      if (sign == '-' || sign == '+') {
-        strlcpy(buf, name, sizeof(buf));
-        buf[3] = (sign == '-') ? '+' : '-';
-        name = buf;
-      }
-    }
+    __bionic_get_system_tz(buf, sizeof(buf));
+    name = buf;
   }
 
-  // If the system property is also not available (because you're running AOSP on a WiFi-only
-  // device, say), fall back to GMT.
-  if (name == nullptr) name = "GMT";
-
   tzsetlcl(name);
 }
 
@@ -192,14 +211,14 @@
     close(fd);
     // This file descriptor (-1) is passed to localtime.c. In invalid fd case
     // upstream passes errno value around methods and having 0 there will
-    // indicate that time zone was found and read successfully and localtime's
+    // indicate that timezone was found and read successfully and localtime's
     // internal state was properly initialized (which wasn't as we couldn't find
-    // requested time zone in the tzdata file).
+    // requested timezone in the tzdata file).
     // If we reached this point errno is unlikely to be touched. It is only
     // close(fd) which can do it, but that is very unlikely to happen. And
     // even if it happens we can't extract any useful insights from it.
     // We are overriding it to ENOENT as it matches upstream expectations -
-    // time zone is absent in the tzdata file == there is no TZif file in
+    // timezone is absent in the tzdata file == there is no TZif file in
     // /usr/share/zoneinfo.
     errno = ENOENT;
     return -1;
@@ -219,7 +238,7 @@
   int fd;
 
   // Try the two locations for the tzdata file in a strict order:
-  // 1: The time zone data module which contains the main copy. This is the
+  // 1: The timezone data module which contains the main copy. This is the
   //    common case for current devices.
   // 2: The ultimate fallback: the non-updatable copy in /system.
 
diff --git a/libc/tzcode/localtime.c b/libc/tzcode/localtime.c
index 5e1181f..018acad 100644
--- a/libc/tzcode/localtime.c
+++ b/libc/tzcode/localtime.c
@@ -374,8 +374,11 @@
      + 4 * TZ_MAX_TIMES];
 };
 
-// Android-removed: There is no directory with file-per-time zone on Android.
-#ifndef __BIONIC__
+#if defined(__BIONIC__)
+// Android: there is no directory with one file per timezone on Android,
+// but we do have a system property instead.
+#include <sys/system_properties.h>
+#else
 /* TZDIR with a trailing '/' rather than a trailing '\0'.  */
 static char const tzdirslash[sizeof TZDIR] = TZDIR "/";
 #endif
@@ -415,13 +418,20 @@
 #endif
 	register union input_buffer *up = &lsp->u.u;
 	register int tzheadsize = sizeof(struct tzhead);
+	char system_tz_name[PROP_VALUE_MAX];
 
 	sp->goback = sp->goahead = false;
 
 	if (! name) {
+#if defined(__BIONIC__)
+		extern void __bionic_get_system_tz(char* , size_t);
+		__bionic_get_system_tz(system_tz_name, sizeof(system_tz_name));
+		name = system_tz_name;
+#else
 		name = TZDEFAULT;
 		if (! name)
 		  return EINVAL;
+#endif
 	}
 
 #if defined(__BIONIC__)