Skip to content

Commit 426512e

Browse files
committed
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: chronotope/chrono#499 Test: treehugger Change-Id: I142995a3ab4deff1073a0aa9e63ce8eac850b93d
1 parent fee0b45 commit 426512e

File tree

8 files changed

+117
-57
lines changed

8 files changed

+117
-57
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ libc/
134134
tzcode/
135135
# A modified superset of the IANA tzcode. Most of the modifications relate
136136
# to Android's use of a single file (with corresponding index) to contain
137-
# time zone data.
137+
# timezone data.
138138
zoneinfo/
139-
# Android-format time zone data.
139+
# Android-format timezone data.
140140
# See 'Updating tzdata' later.
141141
```
142142

docs/status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ New libc functions in V (API level 35):
5959
* `timespec_getres` (C23 addition).
6060
* `localtime_rz`, `mktime_z`, `tzalloc`, and `tzfree` (NetBSD
6161
extensions implemented in tzcode, and the "least non-standard"
62-
functions for avoiding $TZ if you need to use multiple time zones in
62+
functions for avoiding $TZ if you need to use multiple timezones in
6363
multi-threaded C).
6464
* `mbsrtowcs_l` and `wcsrtombs_l` aliases for `mbsrtowcs` and `wcsrtombs`.
6565

libc/Android.bp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ cc_library_static {
269269
"-DTHREAD_SAFE=1",
270270
// The name of the tm_gmtoff field in our struct tm.
271271
"-DTM_GMTOFF=tm_gmtoff",
272-
// TZDEFAULT is not applicable to Android as there is no one file per time zone mapping
272+
// Android uses a system property instead of /etc/localtime, so make callers crash.
273273
"-DTZDEFAULT=NULL",
274274
// Where we store our tzdata.
275275
"-DTZDIR=\"/system/usr/share/zoneinfo\"",

libc/include/time.h

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,22 @@ __BEGIN_DECLS
4242
/* If we just use void* in the typedef, the compiler exposes that in error messages. */
4343
struct __timezone_t;
4444

45-
/** The `timezone_t` type that represents a time zone. */
45+
/** The `timezone_t` type that represents a timezone. */
4646
typedef struct __timezone_t* timezone_t;
4747

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

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

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

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

6363
struct sigevent;
@@ -86,7 +86,7 @@ struct tm {
8686
int tm_isdst;
8787
/** Offset from UTC (GMT) in seconds for this time. */
8888
long int tm_gmtoff;
89-
/** Name of the time zone for this time. */
89+
/** Name of the timezone for this time. */
9090
const char* _Nullable tm_zone;
9191
};
9292

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

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

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

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

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

302309
/**
303-
* tzfree(3) frees a time zone object returned by tzalloc().
310+
* tzfree(3) frees a timezone object returned by tzalloc().
304311
*
305312
* Available since API level 35.
306313
*/

libc/system_properties/context_lookup_benchmark_data.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ persist.odm. u:object_r:vendor_default_prop:s0
321321
persist.vendor. u:object_r:vendor_default_prop:s0
322322
vendor. u:object_r:vendor_default_prop:s0
323323

324-
# Properties that relate to time / time zone detection behavior.
324+
# Properties that relate to time / timezone detection behavior.
325325
persist.time. u:object_r:time_prop:s0
326326

327327
# Properties that relate to server configurable flags

libc/tzcode/bionic.cpp

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,37 +36,44 @@
3636
#include "private/CachedProperty.h"
3737

3838
extern "C" void tzset_unlocked(void);
39+
extern "C" void __bionic_get_system_tz(char* buf, size_t n);
3940
extern "C" int __bionic_open_tzdata(const char*, int32_t*);
4041

4142
extern "C" void tzsetlcl(char const*);
4243

44+
void __bionic_get_system_tz(char* buf, size_t n) {
45+
static CachedProperty persist_sys_timezone("persist.sys.timezone");
46+
const char* name = persist_sys_timezone.Get();
47+
48+
// If the system property is not available (because you're running AOSP on a WiFi-only
49+
// device, say), fall back to GMT.
50+
if (name == nullptr) name = "GMT";
51+
52+
strlcpy(buf, name, n);
53+
54+
if (!strcmp(buf, "GMT")) {
55+
// POSIX and Java disagree about the sign in a timezone string. For POSIX, "GMT+3" means
56+
// "3 hours west/behind", but for Java it means "3 hours east/ahead". Since (a) Java is
57+
// the one that matches human expectations and (b) this system property is used directly
58+
// by Java, we flip the sign here to translate from Java to POSIX. http://b/25463955.
59+
char sign = buf[3];
60+
if (sign == '-' || sign == '+') {
61+
buf[3] = (sign == '-') ? '+' : '-';
62+
}
63+
}
64+
}
65+
4366
void tzset_unlocked() {
4467
// The TZ environment variable is meant to override the system-wide setting.
4568
const char* name = getenv("TZ");
4669
char buf[PROP_VALUE_MAX];
4770

4871
// If that's not set, look at the "persist.sys.timezone" system property.
4972
if (name == nullptr) {
50-
static CachedProperty persist_sys_timezone("persist.sys.timezone");
51-
52-
if ((name = persist_sys_timezone.Get()) != nullptr && strlen(name) > 3) {
53-
// POSIX and Java disagree about the sign in a timezone string. For POSIX, "GMT+3" means
54-
// "3 hours west/behind", but for Java it means "3 hours east/ahead". Since (a) Java is
55-
// the one that matches human expectations and (b) this system property is used directly
56-
// by Java, we flip the sign here to translate from Java to POSIX. http://b/25463955.
57-
char sign = name[3];
58-
if (sign == '-' || sign == '+') {
59-
strlcpy(buf, name, sizeof(buf));
60-
buf[3] = (sign == '-') ? '+' : '-';
61-
name = buf;
62-
}
63-
}
73+
__bionic_get_system_tz(buf, sizeof(buf));
74+
name = buf;
6475
}
6576

66-
// If the system property is also not available (because you're running AOSP on a WiFi-only
67-
// device, say), fall back to GMT.
68-
if (name == nullptr) name = "GMT";
69-
7077
tzsetlcl(name);
7178
}
7279

@@ -192,14 +199,14 @@ static int __bionic_open_tzdata_path(const char* path,
192199
close(fd);
193200
// This file descriptor (-1) is passed to localtime.c. In invalid fd case
194201
// upstream passes errno value around methods and having 0 there will
195-
// indicate that time zone was found and read successfully and localtime's
202+
// indicate that timezone was found and read successfully and localtime's
196203
// internal state was properly initialized (which wasn't as we couldn't find
197-
// requested time zone in the tzdata file).
204+
// requested timezone in the tzdata file).
198205
// If we reached this point errno is unlikely to be touched. It is only
199206
// close(fd) which can do it, but that is very unlikely to happen. And
200207
// even if it happens we can't extract any useful insights from it.
201208
// We are overriding it to ENOENT as it matches upstream expectations -
202-
// time zone is absent in the tzdata file == there is no TZif file in
209+
// timezone is absent in the tzdata file == there is no TZif file in
203210
// /usr/share/zoneinfo.
204211
errno = ENOENT;
205212
return -1;
@@ -219,7 +226,7 @@ int __bionic_open_tzdata(const char* olson_id, int32_t* entry_length) {
219226
int fd;
220227

221228
// Try the two locations for the tzdata file in a strict order:
222-
// 1: The time zone data module which contains the main copy. This is the
229+
// 1: The timezone data module which contains the main copy. This is the
223230
// common case for current devices.
224231
// 2: The ultimate fallback: the non-updatable copy in /system.
225232

libc/tzcode/localtime.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,11 @@ union input_buffer {
374374
+ 4 * TZ_MAX_TIMES];
375375
};
376376

377-
// Android-removed: There is no directory with file-per-time zone on Android.
378-
#ifndef __BIONIC__
377+
#if defined(__BIONIC__)
378+
// Android: there is no directory with file-per-time zone on Android,
379+
// but we do have a system property instead.
380+
#include <sys/system_properties.h>
381+
#else
379382
/* TZDIR with a trailing '/' rather than a trailing '\0'. */
380383
static char const tzdirslash[sizeof TZDIR] = TZDIR "/";
381384
#endif
@@ -415,13 +418,20 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
415418
#endif
416419
register union input_buffer *up = &lsp->u.u;
417420
register int tzheadsize = sizeof(struct tzhead);
421+
char system_tz_name[PROP_VALUE_MAX];
418422

419423
sp->goback = sp->goahead = false;
420424

421425
if (! name) {
426+
#if defined(__BIONIC__)
427+
extern void __bionic_get_system_tz(char* , size_t);
428+
__bionic_get_system_tz(system_tz_name, sizeof(system_tz_name));
429+
name = system_tz_name;
430+
#else
422431
name = TZDEFAULT;
423432
if (! name)
424433
return EINVAL;
434+
#endif
425435
}
426436

427437
#if defined(__BIONIC__)

tests/time_test.cpp

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ TEST(time, mktime_TZ_as_UTC_and_offset) {
9797

9898
static void* gmtime_no_stack_overflow_14313703_fn(void*) {
9999
const char* original_tz = getenv("TZ");
100-
// Ensure we'll actually have to enter tzload by using a time zone that doesn't exist.
100+
// Ensure we'll actually have to enter tzload by using a timezone that doesn't exist.
101101
setenv("TZ", "gmtime_stack_overflow_14313703", 1);
102102
tzset();
103103
if (original_tz != nullptr) {
@@ -324,7 +324,7 @@ TEST(time, strftime_Z_null_tm_zone) {
324324

325325
// According to C language specification the only tm struct field needed to
326326
// find out replacement for %z and %Z in strftime is tm_isdst. Which is
327-
// wrong, as time zones change their standard offset and even DST savings.
327+
// wrong, as timezones change their standard offset and even DST savings.
328328
// tzcode deviates from C language specification and requires tm struct either
329329
// to be output of localtime-like functions or to be modified by mktime call
330330
// before passing to strftime. See tz mailing discussion for more details
@@ -560,7 +560,7 @@ TEST(time, strptime_Z) {
560560
EXPECT_EQ(1, tm.tm_isdst);
561561
EXPECT_EQ(3600, tm.tm_gmtoff);
562562

563-
// And as long as we're in Europe/Berlin, those are the only time zone
563+
// And as long as we're in Europe/Berlin, those are the only timezone
564564
// abbreviations that are recognized.
565565
tm = {};
566566
ASSERT_TRUE(strptime("PDT", "%Z", &tm) == nullptr);
@@ -1118,7 +1118,7 @@ TEST(time, bug_31938693) {
11181118
// Actual underlying bug (the code change, not the tzdata upgrade that first exposed the bug):
11191119
// http://b/31848040
11201120

1121-
// This isn't a great test, because very few time zones were actually affected, and there's
1121+
// This isn't a great test, because very few timezones were actually affected, and there's
11221122
// no real logic to which ones were affected: it was just a coincidence of the data that came
11231123
// after them in the tzdata file.
11241124

@@ -1159,10 +1159,10 @@ TEST(time, bug_31938693) {
11591159
TEST(time, bug_31339449) {
11601160
// POSIX says localtime acts as if it calls tzset.
11611161
// tzset does two things:
1162-
// 1. it sets the time zone ctime/localtime/mktime/strftime will use.
1162+
// 1. it sets the timezone ctime/localtime/mktime/strftime will use.
11631163
// 2. it sets the global `tzname`.
11641164
// POSIX says localtime_r need not set `tzname` (2).
1165-
// Q: should localtime_r set the time zone (1)?
1165+
// Q: should localtime_r set the timezone (1)?
11661166
// Upstream tzcode (and glibc) answer "no", everyone else answers "yes".
11671167

11681168
// Pick a time, any time...
@@ -1361,7 +1361,7 @@ TEST(time, localtime_rz) {
13611361
ASSERT_EQ(&tm, localtime_rz(seoul, &t, &tm));
13621362
AssertTmEq(tm, 17);
13631363

1364-
// Just check that mktime()'s time zone didn't change.
1364+
// Just check that mktime()'s timezone didn't change.
13651365
tm = {};
13661366
ASSERT_EQ(&tm, localtime_r(&t, &tm));
13671367
ASSERT_EQ(0, tm.tm_hour);
@@ -1403,7 +1403,7 @@ TEST(time, mktime_z) {
14031403
tm = {.tm_year = 93, .tm_mday = 1};
14041404
ASSERT_EQ(725814000, mktime_z(seoul, &tm));
14051405

1406-
// Just check that mktime()'s time zone didn't change.
1406+
// Just check that mktime()'s timezone didn't change.
14071407
tm = {.tm_year = 93, .tm_mday = 1};
14081408
ASSERT_EQ(725875200, mktime(&tm));
14091409

@@ -1416,3 +1416,39 @@ TEST(time, mktime_z) {
14161416
GTEST_SKIP() << "glibc doesn't have timezone_t";
14171417
#endif
14181418
}
1419+
1420+
TEST(time, tzalloc_nullptr) {
1421+
#if __BIONIC__
1422+
// tzalloc(nullptr) returns the system timezone.
1423+
timezone_t default_tz = tzalloc(nullptr);
1424+
ASSERT_NE(nullptr, default_tz);
1425+
1426+
// Check that mktime_z() with the default timezone matches mktime().
1427+
// This assumes that the system timezone doesn't change during the test,
1428+
// but that should be unlikely, and we don't have much choice if we
1429+
// want to write a test at all.
1430+
// We unset $TZ before calling mktime() because mktime() honors $TZ.
1431+
unsetenv("TZ");
1432+
struct tm tm = {.tm_year = 93, .tm_mday = 1};
1433+
time_t t = mktime(&tm);
1434+
ASSERT_EQ(t, mktime_z(default_tz, &tm));
1435+
1436+
// Check that changing $TZ doesn't affect the tzalloc() default in
1437+
// the same way it would the mktime() default.
1438+
setenv("TZ", "America/Los_Angeles", 1);
1439+
tzset();
1440+
ASSERT_EQ(t, mktime_z(default_tz, &tm));
1441+
1442+
setenv("TZ", "Europe/London", 1);
1443+
tzset();
1444+
ASSERT_EQ(t, mktime_z(default_tz, &tm));
1445+
1446+
setenv("TZ", "Asia/Seoul", 1);
1447+
tzset();
1448+
ASSERT_EQ(t, mktime_z(default_tz, &tm));
1449+
1450+
tzfree(default_tz);
1451+
#else
1452+
GTEST_SKIP() << "glibc doesn't have timezone_t";
1453+
#endif
1454+
}

0 commit comments

Comments
 (0)