From 31097e2b996ed463ca97d3df618a614c875386c5 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Tue, 29 Jun 2021 09:13:22 -0400 Subject: [PATCH 1/3] time: simplify get_timezones() The function can be simplified by using extract_many_words() and strv_extend() --- src/basic/time-util.c | 56 +++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 48e4d8dea8..c6bdf01d12 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1265,23 +1265,14 @@ int parse_nsec(const char *t, nsec_t *nsec) { int get_timezones(char ***ret) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **zones = NULL; - size_t n_zones = 0; int r; assert(ret); - zones = strv_new("UTC"); - if (!zones) - return -ENOMEM; - - n_zones = 1; - f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); if (f) { for (;;) { - _cleanup_free_ char *line = NULL, *w = NULL; - char *p; - size_t k; + _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL; r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) @@ -1289,43 +1280,34 @@ int get_timezones(char ***ret) { if (r == 0) break; - p = strstrip(line); + const char *p = line; - if (isempty(p) || *p == '#') + /* Line format is: + * 'country codes' 'coordinates' 'timezone' 'comments' */ + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + if (r < 0) continue; - /* Skip over country code */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Skip over coordinates */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Found timezone name */ - k = strcspn(p, WHITESPACE); - if (k <= 0) + /* Lines that start with # are comments. */ + if (*cc == '#') continue; - w = strndup(p, k); - if (!w) - return -ENOMEM; - - if (!GREEDY_REALLOC(zones, n_zones + 2)) - return -ENOMEM; - - zones[n_zones++] = TAKE_PTR(w); - zones[n_zones] = NULL; + r = strv_extend(&zones, tz); + if (r < 0) + return r; } - - strv_sort(zones); - strv_uniq(zones); - } else if (errno != ENOENT) return -errno; - *ret = TAKE_PTR(zones); + /* Always include UTC */ + r = strv_extend(&zones, "UTC"); + if (r < 0) + return -ENOMEM; + strv_sort(zones); + strv_uniq(zones); + + *ret = TAKE_PTR(zones); return 0; } From 09a54a862b8f45cff087eb4eabbd283d354afc90 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Wed, 30 Jun 2021 07:17:22 -0400 Subject: [PATCH 2/3] time: split get_timezone() into main function and zone1970.tab function This allows for adding another function to read from a different timezone source, which is added in the next commit. --- src/basic/time-util.c | 70 ++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index c6bdf01d12..c874cfec89 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1262,7 +1262,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { return 0; } -int get_timezones(char ***ret) { +static int get_timezones_from_zone1970_tab(char ***ret) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **zones = NULL; int r; @@ -1270,35 +1270,49 @@ int get_timezones(char ***ret) { assert(ret); f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); - if (f) { - for (;;) { - _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL; - - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) - break; - - const char *p = line; - - /* Line format is: - * 'country codes' 'coordinates' 'timezone' 'comments' */ - r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); - if (r < 0) - continue; - - /* Lines that start with # are comments. */ - if (*cc == '#') - continue; - - r = strv_extend(&zones, tz); - if (r < 0) - return r; - } - } else if (errno != ENOENT) + if (!f) return -errno; + for (;;) { + _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + const char *p = line; + + /* Line format is: + * 'country codes' 'coordinates' 'timezone' 'comments' */ + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + if (r < 0) + continue; + + /* Lines that start with # are comments. */ + if (*cc == '#') + continue; + + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(zones); + return 0; +} + +int get_timezones(char ***ret) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + assert(ret); + + r = get_timezones_from_zone1970_tab(&zones); + if (r < 0 && r != -ENOENT) + return r; + /* Always include UTC */ r = strv_extend(&zones, "UTC"); if (r < 0) From 147bc3639b3d7b15fc7b548b24715e7c4d95c6e1 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Wed, 30 Jun 2021 07:30:28 -0400 Subject: [PATCH 3/3] time: get timezones from tzdata.zi The zone1970.tab file doesn't include any timezone 'aliases'. Instead of parsing it, parse the tzdata.zi file which does include all zones as well as aliases. This keeps the parsing function for zone1970.tab as a fallback in case the tzdata.zi file isn't found. --- src/basic/time-util.c | 58 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/basic/time-util.c b/src/basic/time-util.c index c874cfec89..b2c86a0e41 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1303,13 +1303,69 @@ static int get_timezones_from_zone1970_tab(char ***ret) { return 0; } +static int get_timezones_from_tzdata_zi(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + int r; + + f = fopen("/usr/share/zoneinfo/tzdata.zi", "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + const char *p = line; + + /* The only lines we care about are Zone and Link lines. + * Zone line format is: + * 'Zone' 'timezone' ... + * Link line format is: + * 'Link' 'target' 'alias' + * See 'man zic' for more detail. */ + r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL); + if (r < 0) + continue; + + char *tz; + if (*type == 'Z' || *type == 'z') + /* Zone lines have timezone in field 1. */ + tz = f1; + else if (*type == 'L' || *type == 'l') + /* Link lines have timezone in field 2. */ + tz = f2; + else + /* Not a line we care about. */ + continue; + + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(zones); + return 0; +} + int get_timezones(char ***ret) { _cleanup_strv_free_ char **zones = NULL; int r; assert(ret); - r = get_timezones_from_zone1970_tab(&zones); + r = get_timezones_from_tzdata_zi(&zones); + if (r == -ENOENT) { + log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m"); + r = get_timezones_from_zone1970_tab(&zones); + if (r == -ENOENT) + log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m"); + } if (r < 0 && r != -ENOENT) return r;