Skip to content

Commit

Permalink
Fix tzalloc(nullptr) and add a test.
Browse files Browse the repository at this point in the history
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: chronotope/chrono#499
Test: treehugger
Change-Id: I142995a3ab4deff1073a0aa9e63ce8eac850b93d
  • Loading branch information
enh-google committed Jun 22, 2023
1 parent a43db27 commit 31fc69f
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 58 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ libc/
tzcode/
# A modified superset of the IANA tzcode. Most of the modifications relate
# to Android's use of a single file (with corresponding index) to contain
# time zone data.
# timezone data.
zoneinfo/
# Android-format time zone data.
# Android-format timezone data.
# See 'Updating tzdata' later.
```

Expand Down
2 changes: 1 addition & 1 deletion docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ New libc functions in V (API level 35):
* `timespec_getres` (C23 addition).
* `localtime_rz`, `mktime_z`, `tzalloc`, and `tzfree` (NetBSD
extensions implemented in tzcode, and the "least non-standard"
functions for avoiding $TZ if you need to use multiple time zones in
functions for avoiding $TZ if you need to use multiple timezones in
multi-threaded C).
* `mbsrtowcs_l` and `wcsrtombs_l` aliases for `mbsrtowcs` and `wcsrtombs`.

Expand Down
2 changes: 1 addition & 1 deletion libc/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ cc_library_static {
"-DTHREAD_SAFE=1",
// The name of the tm_gmtoff field in our struct tm.
"-DTM_GMTOFF=tm_gmtoff",
// TZDEFAULT is not applicable to Android as there is no one file per time zone mapping
// Android uses a system property instead of /etc/localtime, so make callers crash.
"-DTZDEFAULT=NULL",
// Where we store our tzdata.
"-DTZDIR=\"/system/usr/share/zoneinfo\"",
Expand Down
49 changes: 28 additions & 21 deletions libc/include/time.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,22 @@ __BEGIN_DECLS
/* If we just use void* in the typedef, the compiler exposes that in error messages. */
struct __timezone_t;

/** The `timezone_t` type that represents a time zone. */
/** The `timezone_t` type that represents a timezone. */
typedef struct __timezone_t* timezone_t;

/** Divisor to compute seconds from the result of a call to clock(). */
#define CLOCKS_PER_SEC 1000000

/**
* The name of the current time zone's non-daylight savings (`tzname[0]`) and
* The name of the current timezone's non-daylight savings (`tzname[0]`) and
* daylight savings (`tzname[1]`) variants. See tzset().
*/
extern char* _Nonnull tzname[];

/** Whether the current time zone ever uses daylight savings time. See tzset(). */
/** Whether the current timezone ever uses daylight savings time. See tzset(). */
extern int daylight;

/** The difference in seconds between UTC and the current time zone. See tzset(). */
/** The difference in seconds between UTC and the current timezone. See tzset(). */
extern long int timezone;

struct sigevent;
Expand Down Expand Up @@ -86,7 +86,7 @@ struct tm {
int tm_isdst;
/** Offset from UTC (GMT) in seconds for this time. */
long int tm_gmtoff;
/** Name of the time zone for this time. */
/** Name of the timezone for this time. */
const char* _Nullable tm_zone;
};

Expand Down Expand Up @@ -145,7 +145,7 @@ double difftime(time_t __lhs, time_t __rhs);
* [mktime(3)](http://man7.org/linux/man-pages/man3/mktime.3p.html) converts
* broken-down time `tm` into the number of seconds since the Unix epoch.
*
* See tzset() for details of how the time zone is set, and mktime_rz()
* See tzset() for details of how the timezone is set, and mktime_rz()
* for an alternative.
*
* Returns the time in seconds on success, and returns -1 and sets `errno` on failure.
Expand All @@ -154,7 +154,7 @@ time_t mktime(struct tm* _Nonnull __tm);

/**
* mktime_z(3) converts broken-down time `tm` into the number of seconds
* since the Unix epoch, assuming the given time zone.
* since the Unix epoch, assuming the given timezone.
*
* Returns the time in seconds on success, and returns -1 and sets `errno` on failure.
*
Expand All @@ -178,7 +178,7 @@ struct tm* _Nullable localtime(const time_t* _Nonnull __t);
* the number of seconds since the Unix epoch in `t` to a broken-down time.
* That broken-down time will be written to the given struct `tm`.
*
* See tzset() for details of how the time zone is set, and localtime_rz()
* See tzset() for details of how the timezone is set, and localtime_rz()
* for an alternative.
*
* Returns a pointer to a broken-down time on success, and returns null and sets `errno` on failure.
Expand All @@ -187,7 +187,7 @@ struct tm* _Nullable localtime_r(const time_t* _Nonnull __t, struct tm* _Nonnull

/**
* localtime_rz(3) converts the number of seconds since the Unix epoch in
* `t` to a broken-down time, assuming the given time zone. That broken-down
* `t` to a broken-down time, assuming the given timezone. That broken-down
* time will be written to the given struct `tm`.
*
* Returns a pointer to a broken-down time on success, and returns null and sets `errno` on failure.
Expand Down Expand Up @@ -278,29 +278,36 @@ char* _Nullable ctime_r(const time_t* _Nonnull __t, char* _Nonnull __buf);

/**
* [tzset(3)](http://man7.org/linux/man-pages/man3/tzset.3.html) tells
* libc that the time zone has changed.
* libc that the timezone has changed.
*
* Android looks at both the system property `persist.sys.timezone` and the
* environment variable `TZ`. The former is the device's current time zone
* as shown in Settings, while the latter is usually unset but can be used
* to override the global setting. This is a bad idea outside of unit tests
* or single-threaded programs because it's inherently thread-unsafe.
* See tzalloc(), localtime_rz(), mktime_z(), and tzfree() for an
* alternative.
* tzset() on Android looks at both the system property
* `persist.sys.timezone` and the environment variable `TZ`. The former is
* the device's current timezone as shown in Settings, while the latter is
* usually unset but can be used to override the global setting. This is a
* bad idea outside of unit tests or single-threaded programs because it's
* inherently thread-unsafe. See tzalloc(), localtime_rz(), mktime_z(),
* and tzfree() for an alternative.
*/
void tzset(void);

/**
* tzalloc(3) allocates a time zone corresponding to the given Olson id.
* tzalloc(3) allocates a timezone corresponding to the given Olson ID.
*
* Returns a time zone object on success, and returns NULL and sets `errno` on failure.
* A null `id` returns the system timezone (as seen in Settings) from
* the system property `persist.sys.timezone`, ignoring `$TZ`. Although
* tzset() honors `$TZ`, callers of tzalloc() can use `$TZ` themselves if
* that's the (thread-unsafe) behavior they want, but by ignoring `$TZ`
* tzalloc() is thread safe (though obviously the system timezone can
* change, especially if your mobile device is actually mobile!).
*
* Returns a timezone object on success, and returns NULL and sets `errno` on failure.
*
* Available since API level 35.
*/
timezone_t _Nullable tzalloc(const char* _Nullable __id) __INTRODUCED_IN(35);

/**
* tzfree(3) frees a time zone object returned by tzalloc().
* tzfree(3) frees a timezone object returned by tzalloc().
*
* Available since API level 35.
*/
Expand All @@ -320,7 +327,7 @@ clock_t clock(void);

/**
* [clock_getcpuclockid(3)](http://man7.org/linux/man-pages/man3/clock_getcpuclockid.3.html)
* gets the clock id of the cpu-time clock for the given `pid`.
* gets the clock ID of the cpu-time clock for the given `pid`.
*
* Returns 0 on success, and returns -1 and returns an error number on failure.
*/
Expand Down
2 changes: 1 addition & 1 deletion libc/system_properties/context_lookup_benchmark_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ persist.odm. u:object_r:vendor_default_prop:s0
persist.vendor. u:object_r:vendor_default_prop:s0
vendor. u:object_r:vendor_default_prop:s0

# Properties that relate to time / time zone detection behavior.
# Properties that relate to time / timezone detection behavior.
persist.time. u:object_r:time_prop:s0

# Properties that relate to server configurable flags
Expand Down
63 changes: 41 additions & 22 deletions libc/tzcode/bionic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,37 +36,56 @@
#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");
char buf[PROP_VALUE_MAX];

// 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);
}

Expand Down Expand Up @@ -192,14 +211,14 @@ static int __bionic_open_tzdata_path(const char* path,
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;
Expand All @@ -219,7 +238,7 @@ int __bionic_open_tzdata(const char* olson_id, int32_t* entry_length) {
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.

Expand Down
14 changes: 12 additions & 2 deletions libc/tzcode/localtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,11 @@ union input_buffer {
+ 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
Expand Down Expand Up @@ -415,13 +418,20 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
#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__)
Expand Down
Loading

0 comments on commit 31fc69f

Please sign in to comment.