#include <err.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libzoneinfo.h>
#include <sys/debug.h>
#include <errno.h>
#include <string.h>
static void
usage(const char *fmt, ...)
{
if (fmt != NULL) {
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
(void) fprintf(stderr, "Usage: zoneinfo_dump -d dir\n");
}
static void
dump_timezone(int dirfd, const struct tz_continent *cont,
const struct tz_country *country, const struct tz_timezone *tz)
{
int fd;
char *name;
FILE *f;
name = strdup(tz->tz_name);
if (name == NULL) {
err(EXIT_FAILURE, "failed to duplicate tz name %s",
tz->tz_name);
}
for (size_t i = 0; i < strlen(name); i++) {
if (name[i] == '/')
name[i] = '-';
}
if ((fd = openat(dirfd, name, O_RDWR| O_CREAT | O_TRUNC, 0644)) < 0) {
err(EXIT_FAILURE, "failed to create tz file for %s/%s/%s",
cont->ctnt_name, country->ctry_code, name);
}
if ((f = fdopen(fd, "w")) == NULL) {
err(EXIT_FAILURE, "failed to create stdio stream for "
"tz %s/%s/%s file", cont->ctnt_name, country->ctry_code,
name);
}
(void) fprintf(f, "name: %s\n", tz->tz_name);
(void) fprintf(f, "oname: %s\n", tz->tz_oname);
(void) fprintf(f, "id: %s\n", tz->tz_id_desc);
(void) fprintf(f, "desc: %s\n", tz->tz_display_desc);
(void) fprintf(f, "lat: %d.%u.%u.%u\n", tz->tz_coord.lat_sign,
tz->tz_coord.lat_degree, tz->tz_coord.lat_minute,
tz->tz_coord.lat_second);
(void) fprintf(f, "long: %d.%u.%u.%u\n", tz->tz_coord.long_sign,
tz->tz_coord.long_degree, tz->tz_coord.long_minute,
tz->tz_coord.long_second);
VERIFY0(fflush(f));
VERIFY0(fclose(f));
free(name);
}
static void
dump_country(int cfd, const struct tz_continent *cont,
struct tz_country *country)
{
int dirfd, infofd, ret, found = 0;
struct tz_timezone *zones;
FILE *f;
if (mkdirat(cfd, country->ctry_code, 0755) != 0 && errno != EEXIST) {
err(EXIT_FAILURE, "failed to make country directory %s/%s",
cont->ctnt_name, country->ctry_code);
}
if ((dirfd = openat(cfd, country->ctry_code, O_DIRECTORY)) < 0) {
err(EXIT_FAILURE, "failed to open country %s/%s",
cont->ctnt_name, country->ctry_code);
}
if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) <
0) {
err(EXIT_FAILURE, "failed to create info file for country "
"%s/%s", cont->ctnt_name, country->ctry_code);
}
if ((f = fdopen(infofd, "w")) == NULL) {
err(EXIT_FAILURE, "failed to create stdio stream for "
"country %s/%s info file", cont->ctnt_name,
country->ctry_code);
}
(void) fprintf(f, "name: %s\n", country->ctry_code);
(void) fprintf(f, "id: %s\n", country->ctry_id_desc);
(void) fprintf(f, "desc: %s\n", country->ctry_display_desc);
VERIFY0(country->ctry_status);
VERIFY0(fflush(f));
VERIFY0(fclose(f));
ret = get_timezones_by_country(&zones, country);
if (ret < 0) {
err(EXIT_FAILURE, "failed to get timezones for country %s/%s",
cont->ctnt_name, country->ctry_code);
}
for (struct tz_timezone *t = zones; t != NULL; t = t->tz_next,
found++) {
dump_timezone(dirfd, cont, country, t);
}
if (ret != found) {
errx(EXIT_FAILURE, "zoneinfo said %u timezones should exist "
"for country %s/%s, but found %u\n", ret, cont->ctnt_name,
country->ctry_code, found);
}
VERIFY0(free_timezones(zones));
VERIFY0(close(dirfd));
}
static void
dump_continent(int root, struct tz_continent *cont)
{
int dirfd, infofd, ret, found = 0;
struct tz_country *country;
FILE *f;
if (mkdirat(root, cont->ctnt_name, 0755) != 0 &&
errno != EEXIST) {
err(EXIT_FAILURE, "failed to make continent %s",
cont->ctnt_name);
}
if ((dirfd = openat(root, cont->ctnt_name, O_DIRECTORY)) < 0) {
err(EXIT_FAILURE, "failed to open continent %s",
cont->ctnt_name);
}
if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) <
0) {
err(EXIT_FAILURE, "failed to create info file for continent "
"%s", cont->ctnt_name);
}
if ((f = fdopen(infofd, "w")) == NULL) {
err(EXIT_FAILURE, "failed to create stdio stream for "
"continent %s info file", cont->ctnt_name);
}
(void) fprintf(f, "name: %s\n", cont->ctnt_name);
(void) fprintf(f, "id: %s\n", cont->ctnt_id_desc);
(void) fprintf(f, "desc: %s\n", cont->ctnt_display_desc);
VERIFY0(fflush(f));
VERIFY0(fclose(f));
ret = get_tz_countries(&country, cont);
if (ret < 0) {
err(EXIT_FAILURE, "failed to get countries for continent %s",
cont->ctnt_name);
}
for (struct tz_country *c = country; c != NULL; c = c->ctry_next,
found++) {
dump_country(dirfd, cont, c);
}
if (ret != found) {
errx(EXIT_FAILURE, "zoneinfo said %u countries should exist "
"for continent %s, but found %u\n", ret, cont->ctnt_name,
found);
}
VERIFY0(free_tz_countries(country));
VERIFY0(close(dirfd));
}
int
main(int argc, char *argv[])
{
int c, dirfd, ret, found = 0;
const char *base = NULL;
struct tz_continent *conts;
while ((c = getopt(argc, argv, ":d:")) != -1) {
switch (c) {
case 'd':
base = optarg;
break;
case '?':
usage("option -%c requires an argument", optopt);
exit(EXIT_FAILURE);
case ':':
usage("unknown option: -%c", optopt);
exit(EXIT_FAILURE);
}
}
if (base == NULL) {
errx(EXIT_FAILURE, "missing required directory, please use "
"the -d flag");
}
if ((dirfd = open(base, O_RDONLY | O_DIRECTORY)) < 0) {
err(EXIT_FAILURE, "failed to open directory %s", base);
}
ret = get_tz_continents(&conts);
if (ret < 0) {
err(EXIT_FAILURE, "failed to get continents");
}
for (struct tz_continent *c = conts; c != NULL; c = c->ctnt_next,
found++) {
dump_continent(dirfd, c);
}
if (found != ret) {
errx(EXIT_FAILURE, "zoneinfo said %u continents should exist, "
"but found %u\n", ret, found);
}
VERIFY0(free_tz_continents(conts));
return (0);
}