74
79
+#define ZONEINFO_PREFIX "/usr/share/zoneinfo"
82
+/* System timezone database pointer. */
83
+static const timelib_tzdb *timezonedb_system = NULL;
77
85
+/* Hash table entry for the cache of the zone.tab mapping table. */
78
86
+struct location_info {
79
+ char code[2]; /* Country code. */
80
+ double latitude, longitude;
83
+ struct location_info *next;
88
+ double latitude, longitude;
91
+ struct location_info *next;
86
+/* System timezone database pointer. */
87
+static const timelib_tzdb *timezonedb_system = NULL;
89
+/* Cache of zone.tab location data. */
94
+/* Cache of zone.tab. */
90
95
+static struct location_info **system_location_table;
92
97
+/* Size of the zone.tab hash table; a random-ish prime big enough to
93
98
+ * prevent too many collisions. */
94
99
+#define LOCINFO_HASH_SIZE (1021)
96
+/* Hash function for indexing the location_info hash table. */
97
101
+static uint32_t tz_hash(const char *str)
99
+ const unsigned char *p = (const unsigned char *)str;
100
+ uint32_t hash = 5381;
103
+ while ((c = *p++) != '\0') {
104
+ hash = (hash << 5) ^ hash ^ c;
107
+ return hash % LOCINFO_HASH_SIZE;
103
+ const unsigned char *p = (const unsigned char *)str;
104
+ uint32_t hash = 5381;
107
+ while ((c = *p++) != '\0') {
108
+ hash = (hash << 5) ^ hash ^ c;
111
+ return hash % LOCINFO_HASH_SIZE;
110
+/* Parse an ISO-6709 co-ordinate as used in zone.tab. Returns end of
111
+ * the parsed string on success, or NULL on parse error. On success,
114
+/* Parse an ISO-6709 date as used in zone.tab. Returns end of the
115
+ * parsed string on success, or NULL on parse error. On success,
112
116
+ * writes the parsed number to *result. */
113
117
+static char *parse_iso6709(char *p, double *result)
121
+ else if (*p == '-')
127
+ for (pend = p; *pend >= '0' && *pend <= '9'; pend++)
130
+ /* Annoying encoding used by zone.tab has no decimal point, so use
131
+ * the length to determine the format:
139
+ if (len < 4 || len > 7) {
144
+ v = (p[0] - '0') * 10.0 + (p[1] - '0');
146
+ if (len == 5 || len == 7)
147
+ v = v * 10.0 + (*p++ - '0');
149
+ v += (10.0 * (p[0] - '0')
150
+ + p[1] - '0') / 60.0;
154
+ v += (10.0 * (p[0] - '0')
155
+ + p[1] - '0') / 3600.0;
159
+ /* Round to five decimal place, not because it's a good idea,
160
+ * but, because the builtin data uses rounded data, so, match
162
+ *result = sign * (int)(v * 100000.0 + 0.5) / 100000.0;
125
+ else if (*p == '-')
131
+ for (pend = p; *pend >= '0' && *pend <= '9'; pend++)
134
+ /* Annoying encoding used by zone.tab has no decimal point, so use
135
+ * the length to determine the format:
143
+ if (len < 4 || len > 7) {
148
+ v = (p[0] - '0') * 10.0 + (p[1] - '0');
150
+ if (len == 5 || len == 7)
151
+ v = v * 10.0 + (*p++ - '0');
153
+ v += (10.0 * (p[0] - '0')
154
+ + p[1] - '0') / 60.0;
158
+ v += (10.0 * (p[0] - '0')
159
+ + p[1] - '0') / 3600.0;
163
+ /* Round to five decimal place, not because it's a good idea,
164
+ * but, because the builtin data uses rounded data, so, match
166
+ *result = round(v * sign * 100000.0) / 100000.0;
167
171
+/* This function parses the zone.tab file to build up the mapping of
168
172
+ * timezone to country code and geographic location, and returns a
169
+ * hash table. The hash table is indexed by the function:
173
+ * hash table. The hash table is indexed by the function:
171
+ * tz_hash(timezone-name)
175
+ * tz_hash(timezone-name)
173
177
+static struct location_info **create_location_table(void)
175
+ struct location_info **li, *i;
176
+ char zone_tab[PATH_MAX];
180
+ strncpy(zone_tab, ZONEINFO_PREFIX "/zone.tab", sizeof zone_tab);
182
+ fp = fopen(zone_tab, "r");
187
+ li = calloc(LOCINFO_HASH_SIZE, sizeof *li);
189
+ while (fgets(line, sizeof line, fp)) {
190
+ char *p = line, *code, *name, *comment;
192
+ double latitude, longitude;
194
+ while (isspace(*p))
197
+ if (*p == '#' || *p == '\0' || *p == '\n')
200
+ if (!isalpha(p[0]) || !isalpha(p[1]) || p[2] != '\t')
208
+ /* coords => [+-][D]DDMM[SS][+-][D]DDMM[SS] */
209
+ p = parse_iso6709(p, &latitude);
213
+ p = parse_iso6709(p, &longitude);
218
+ if (!p || *p != '\t') {
222
+ /* name = string */
224
+ while (*p != '\t' && *p && *p != '\n')
229
+ /* comment = string */
231
+ while (*p != '\t' && *p && *p != '\n')
234
+ if (*p == '\n' || *p == '\t')
237
+ hash = tz_hash(name);
238
+ i = malloc(sizeof *i);
239
+ memcpy(i->code, code, 2);
240
+ strncpy(i->name, name, sizeof i->name);
241
+ i->comment = strdup(comment);
242
+ i->longitude = longitude;
243
+ i->latitude = latitude;
244
+ i->next = li[hash];
246
+ /* printf("%s [%u, %f, %f]\n", name, hash, latitude, longitude); */
179
+ struct location_info **li, *i;
180
+ char zone_tab[PATH_MAX];
184
+ strncpy(zone_tab, ZONEINFO_PREFIX "/zone.tab", sizeof zone_tab);
186
+ fp = fopen(zone_tab, "r");
191
+ li = calloc(LOCINFO_HASH_SIZE, sizeof *li);
193
+ while (fgets(line, sizeof line, fp)) {
194
+ char *p = line, *code, *name, *comment;
196
+ double latitude, longitude;
198
+ while (isspace(*p))
201
+ if (*p == '#' || *p == '\0' || *p == '\n')
204
+ if (!isalpha(p[0]) || !isalpha(p[1]) || p[2] != '\t')
212
+ /* coords => [+-][D]DDMM[SS][+-][D]DDMM[SS] */
213
+ p = parse_iso6709(p, &latitude);
217
+ p = parse_iso6709(p, &longitude);
222
+ if (!p || *p != '\t') {
226
+ /* name = string */
228
+ while (*p != '\t' && *p && *p != '\n')
233
+ /* comment = string */
235
+ while (*p != '\t' && *p && *p != '\n')
238
+ if (*p == '\n' || *p == '\t')
241
+ hash = tz_hash(name);
242
+ i = malloc(sizeof *i);
243
+ memcpy(i->code, code, 2);
244
+ strncpy(i->name, name, sizeof i->name);
245
+ i->comment = strdup(comment);
246
+ i->longitude = longitude;
247
+ i->latitude = latitude;
248
+ i->next = li[hash];
250
+ /* printf("%s [%u, %f, %f]\n", name, hash, latitude, longitude); */
254
258
+/* Return location info from hash table, using given timezone name.
255
259
+ * Returns NULL if the name could not be found. */
256
260
+const struct location_info *find_zone_info(struct location_info **li,
259
+ uint32_t hash = tz_hash(name);
260
+ const struct location_info *l;
266
+ for (l = li[hash]; l; l = l->next) {
267
+ if (strcasecmp(l->name, name) == 0)
263
+ uint32_t hash = tz_hash(name);
264
+ const struct location_info *l;
270
+ for (l = li[hash]; l; l = l->next) {
271
+ if (strcasecmp(l->name, name) == 0)
274
278
+/* Filter out some non-tzdata files and the posix/right databases, if
380
379
+#define FAKE_HEADER "1234\0??\1??"
381
+#define FAKE_BC_POS (0)
382
380
+#define FAKE_UTC_POS (7 - 4)
384
+/* Create a fake data segment for database 'sysdb'. This mocks
385
+ * up a fake ->data segment for the given timezone database.
386
+ * php_date.c::timezone_identifiers_list() looks at data[pos + 4]
387
+ * through data[pos + 6] to compare the country code and BC flag,
388
+ * which are stored in the builtin data array like:
390
+ * (pos + 4) => BC flag
391
+ * (pos + 5, pos + 6) => Two chars of country code
393
+ * where pos is the index corresponding to the timezone name.
395
+ * Timezone names are classified here into three types:
396
+ * 1) UTC, which is special
397
+ * 2) "normal" zone names
398
+ * 3) "backwards-compat" zone names
400
+ * (boolean logic of the BC flag seems to be inverted, but hey)
402
+ * UTC is special since it has BC=\1, code = "??"
403
+ * "normal" zones exist in zone.tab and have the given c-code and BC=\1
404
+ * "backwards-compat" zones don't exist in zone.tab and have BC=\0
406
+ * Since UTC and the BC zones are constant, they are encoded in the
407
+ * FAKE_HEADER prefix, and pos pointers index into that.
409
+ * FAKE_HEADER is hence four random bytes, then the BC zone segment
410
+ * (three bytes), then the UTC zone segment (another three).
412
+ * For all "normal" zones, three bytes are appended to the data array;
413
+ * the BC flag, always 1, and the two bytes of country code.
382
+/* Create a fake data segment for database 'sysdb'. */
415
383
+static void fake_data_segment(timelib_tzdb *sysdb,
416
+ struct location_info **info)
421
+ /* Worst case maximum is 3 bytes per zone, plus the header. */
422
+ data = malloc((3 * sysdb->index_size) + sizeof(FAKE_HEADER) - 1);
424
+ /* Append the fake header, p then = next byte */
425
+ p = mempcpy(data, FAKE_HEADER, sizeof(FAKE_HEADER) - 1);
427
+ for (n = 0; n < sysdb->index_size; n++) {
428
+ const struct location_info *li;
429
+ timelib_tzdb_index_entry *ent;
431
+ /* Lost const'ness since we're modifying the pos pointer. */
432
+ ent = (timelib_tzdb_index_entry *)&sysdb->index[n];
434
+ /* Lookup the timezone name in the hash table. */
435
+ if (strcmp(ent->id, "UTC") == 0) {
436
+ ent->pos = FAKE_UTC_POS;
440
+ li = find_zone_info(info, ent->id);
442
+ /* If found, append the BC byte and the country code; set
443
+ * the position index for the timezone to point to
445
+ ent->pos = (p - data) - 4;
447
+ *p++ = li->code[0];
448
+ *p++ = li->code[1];
451
+ /* If not found, the timezone data can
452
+ * point at the header. */
457
+ /* Store the fake data array */
458
+ sysdb->data = (unsigned char *)data;
461
+/* Evaluates to true if given timezone name is valid. */
462
+#define is_valid_tz_name(tz_) (tz_[0] && strstr(tz_, "..") == NULL)
464
+/* Return the mmap()ed tzfile if found, else NULL. On success, the
384
+ struct location_info **info)
389
+ data = malloc(3 * sysdb->index_size + 7);
391
+ p = mempcpy(data, FAKE_HEADER, sizeof(FAKE_HEADER) - 1);
393
+ for (n = 0; n < sysdb->index_size; n++) {
394
+ const struct location_info *li;
395
+ timelib_tzdb_index_entry *ent;
397
+ ent = (timelib_tzdb_index_entry *)&sysdb->index[n];
399
+ /* Lookup the timezone name in the hash table. */
400
+ if (strcmp(ent->id, "UTC") == 0) {
401
+ ent->pos = FAKE_UTC_POS;
405
+ li = find_zone_info(info, ent->id);
407
+ /* If found, append the BC byte and the
408
+ * country code; set the position for this
409
+ * section of timezone data. */
410
+ ent->pos = (p - data) - 4;
412
+ *p++ = li->code[0];
413
+ *p++ = li->code[1];
416
+ /* If not found, the timezone data can
417
+ * point at the header. */
422
+ sysdb->data = (unsigned char *)data;
425
+/* Returns true if the passed-in stat structure describes a
426
+ * probably-valid timezone file. */
427
+static int is_valid_tzfile(const struct stat *st)
429
+ return S_ISREG(st->st_mode) && st->st_size > 20;
432
+/* Return the mmap()ed tzfile if found, else NULL. On success, the
465
433
+ * length of the mapped data is placed in *length. */
466
434
+static char *map_tzfile(const char *timezone, size_t *length)
491
459
+ return p != MAP_FAILED ? p : NULL;
495
+/* seek_to_tz_position() for a builtin/external database. */
496
+static int inmem_seek_to_tz_position(const unsigned char **tzf,
497
+ char *timezone, const timelib_tzdb *tzdb)
464
+static int inmem_seek_to_tz_position(const unsigned char **tzf, char *timezone, const timelib_tzdb *tzdb)
499
466
int left = 0, right = tzdb->index_size - 1;
500
467
#ifdef HAVE_SETLOCALE
501
@@ -299,36 +747,131 @@ static int seek_to_tz_position(const uns
468
@@ -299,36 +709,125 @@ static int seek_to_tz_position(const uns
505
+/* Modified seek_to_tz_position wrapper which handles the system
506
+ * database and the builtin/external databases in the same way.
507
+ * Returns zero on failure on non-zero on success. On success, (*map,
508
+ * *maplen) is an mmap'ed region if *map is non-NULL, and must be
509
+ * munmaped after use. */
510
472
+static int seek_to_tz_position(const unsigned char **tzf, char *timezone,
511
+ char **map, size_t *maplen,
512
+ const timelib_tzdb *tzdb)
473
+ char **map, size_t *maplen,
474
+ const timelib_tzdb *tzdb)
514
+#ifdef HAVE_SYSTEM_TZDATA
515
476
+ if (tzdb == timezonedb_system) {