1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2025 Oxide Computer Company 14 */ 15 16 /* 17 * Dump all of the information discovered by libzoneinfo in a way that's usable 18 * for diffing. We use the following directory layout from the root: 19 * 20 * dir: <continent-name> 21 * file: info 22 * dir: <country-name> 23 * file: info 24 * file: <tz-name> 25 */ 26 27 #include <err.h> 28 #include <stdlib.h> 29 #include <unistd.h> 30 #include <sys/types.h> 31 #include <sys/stat.h> 32 #include <fcntl.h> 33 #include <libzoneinfo.h> 34 #include <sys/debug.h> 35 #include <errno.h> 36 #include <string.h> 37 38 static void 39 usage(const char *fmt, ...) 40 { 41 if (fmt != NULL) { 42 va_list ap; 43 44 va_start(ap, fmt); 45 vwarnx(fmt, ap); 46 va_end(ap); 47 } 48 49 (void) fprintf(stderr, "Usage: zoneinfo_dump -d dir\n"); 50 } 51 52 static void 53 dump_timezone(int dirfd, const struct tz_continent *cont, 54 const struct tz_country *country, const struct tz_timezone *tz) 55 { 56 int fd; 57 char *name; 58 FILE *f; 59 60 name = strdup(tz->tz_name); 61 if (name == NULL) { 62 err(EXIT_FAILURE, "failed to duplicate tz name %s", 63 tz->tz_name); 64 } 65 66 for (size_t i = 0; i < strlen(name); i++) { 67 if (name[i] == '/') 68 name[i] = '-'; 69 } 70 71 if ((fd = openat(dirfd, name, O_RDWR| O_CREAT | O_TRUNC, 0644)) < 0) { 72 err(EXIT_FAILURE, "failed to create tz file for %s/%s/%s", 73 cont->ctnt_name, country->ctry_code, name); 74 } 75 76 if ((f = fdopen(fd, "w")) == NULL) { 77 err(EXIT_FAILURE, "failed to create stdio stream for " 78 "tz %s/%s/%s file", cont->ctnt_name, country->ctry_code, 79 name); 80 } 81 82 (void) fprintf(f, "name: %s\n", tz->tz_name); 83 (void) fprintf(f, "oname: %s\n", tz->tz_oname); 84 (void) fprintf(f, "id: %s\n", tz->tz_id_desc); 85 (void) fprintf(f, "desc: %s\n", tz->tz_display_desc); 86 (void) fprintf(f, "lat: %d.%u.%u.%u\n", tz->tz_coord.lat_sign, 87 tz->tz_coord.lat_degree, tz->tz_coord.lat_minute, 88 tz->tz_coord.lat_second); 89 (void) fprintf(f, "long: %d.%u.%u.%u\n", tz->tz_coord.long_sign, 90 tz->tz_coord.long_degree, tz->tz_coord.long_minute, 91 tz->tz_coord.long_second); 92 93 VERIFY0(fflush(f)); 94 VERIFY0(fclose(f)); 95 96 free(name); 97 } 98 99 static void 100 dump_country(int cfd, const struct tz_continent *cont, 101 struct tz_country *country) 102 { 103 int dirfd, infofd, ret, found = 0; 104 struct tz_timezone *zones; 105 FILE *f; 106 107 if (mkdirat(cfd, country->ctry_code, 0755) != 0 && errno != EEXIST) { 108 err(EXIT_FAILURE, "failed to make country directory %s/%s", 109 cont->ctnt_name, country->ctry_code); 110 } 111 112 if ((dirfd = openat(cfd, country->ctry_code, O_DIRECTORY)) < 0) { 113 err(EXIT_FAILURE, "failed to open country %s/%s", 114 cont->ctnt_name, country->ctry_code); 115 } 116 117 if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) < 118 0) { 119 err(EXIT_FAILURE, "failed to create info file for country " 120 "%s/%s", cont->ctnt_name, country->ctry_code); 121 } 122 123 if ((f = fdopen(infofd, "w")) == NULL) { 124 err(EXIT_FAILURE, "failed to create stdio stream for " 125 "country %s/%s info file", cont->ctnt_name, 126 country->ctry_code); 127 } 128 129 (void) fprintf(f, "name: %s\n", country->ctry_code); 130 (void) fprintf(f, "id: %s\n", country->ctry_id_desc); 131 (void) fprintf(f, "desc: %s\n", country->ctry_display_desc); 132 VERIFY0(country->ctry_status); 133 VERIFY0(fflush(f)); 134 VERIFY0(fclose(f)); 135 136 ret = get_timezones_by_country(&zones, country); 137 if (ret < 0) { 138 err(EXIT_FAILURE, "failed to get timezones for country %s/%s", 139 cont->ctnt_name, country->ctry_code); 140 } 141 142 for (struct tz_timezone *t = zones; t != NULL; t = t->tz_next, 143 found++) { 144 dump_timezone(dirfd, cont, country, t); 145 } 146 147 if (ret != found) { 148 errx(EXIT_FAILURE, "zoneinfo said %u timezones should exist " 149 "for country %s/%s, but found %u\n", ret, cont->ctnt_name, 150 country->ctry_code, found); 151 } 152 153 VERIFY0(free_timezones(zones)); 154 VERIFY0(close(dirfd)); 155 } 156 157 static void 158 dump_continent(int root, struct tz_continent *cont) 159 { 160 int dirfd, infofd, ret, found = 0; 161 struct tz_country *country; 162 FILE *f; 163 164 if (mkdirat(root, cont->ctnt_name, 0755) != 0 && 165 errno != EEXIST) { 166 err(EXIT_FAILURE, "failed to make continent %s", 167 cont->ctnt_name); 168 } 169 170 if ((dirfd = openat(root, cont->ctnt_name, O_DIRECTORY)) < 0) { 171 err(EXIT_FAILURE, "failed to open continent %s", 172 cont->ctnt_name); 173 } 174 175 if ((infofd = openat(dirfd, "info", O_RDWR| O_CREAT | O_TRUNC, 0644)) < 176 0) { 177 err(EXIT_FAILURE, "failed to create info file for continent " 178 "%s", cont->ctnt_name); 179 } 180 181 if ((f = fdopen(infofd, "w")) == NULL) { 182 err(EXIT_FAILURE, "failed to create stdio stream for " 183 "continent %s info file", cont->ctnt_name); 184 } 185 186 (void) fprintf(f, "name: %s\n", cont->ctnt_name); 187 (void) fprintf(f, "id: %s\n", cont->ctnt_id_desc); 188 (void) fprintf(f, "desc: %s\n", cont->ctnt_display_desc); 189 VERIFY0(fflush(f)); 190 VERIFY0(fclose(f)); 191 192 ret = get_tz_countries(&country, cont); 193 if (ret < 0) { 194 err(EXIT_FAILURE, "failed to get countries for continent %s", 195 cont->ctnt_name); 196 } 197 198 for (struct tz_country *c = country; c != NULL; c = c->ctry_next, 199 found++) { 200 dump_country(dirfd, cont, c); 201 } 202 203 if (ret != found) { 204 errx(EXIT_FAILURE, "zoneinfo said %u countries should exist " 205 "for continent %s, but found %u\n", ret, cont->ctnt_name, 206 found); 207 } 208 209 /* For each Country */ 210 VERIFY0(free_tz_countries(country)); 211 VERIFY0(close(dirfd)); 212 } 213 214 int 215 main(int argc, char *argv[]) 216 { 217 int c, dirfd, ret, found = 0; 218 const char *base = NULL; 219 struct tz_continent *conts; 220 221 while ((c = getopt(argc, argv, ":d:")) != -1) { 222 switch (c) { 223 case 'd': 224 base = optarg; 225 break; 226 case '?': 227 usage("option -%c requires an argument", optopt); 228 exit(EXIT_FAILURE); 229 case ':': 230 usage("unknown option: -%c", optopt); 231 exit(EXIT_FAILURE); 232 } 233 } 234 235 if (base == NULL) { 236 errx(EXIT_FAILURE, "missing required directory, please use " 237 "the -d flag"); 238 } 239 240 if ((dirfd = open(base, O_RDONLY | O_DIRECTORY)) < 0) { 241 err(EXIT_FAILURE, "failed to open directory %s", base); 242 } 243 244 ret = get_tz_continents(&conts); 245 if (ret < 0) { 246 err(EXIT_FAILURE, "failed to get continents"); 247 } 248 249 for (struct tz_continent *c = conts; c != NULL; c = c->ctnt_next, 250 found++) { 251 dump_continent(dirfd, c); 252 } 253 254 if (found != ret) { 255 errx(EXIT_FAILURE, "zoneinfo said %u continents should exist, " 256 "but found %u\n", ret, found); 257 } 258 259 VERIFY0(free_tz_continents(conts)); 260 261 return (0); 262 } 263