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
usage(const char * fmt,...)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
dump_timezone(int dirfd,const struct tz_continent * cont,const struct tz_country * country,const struct tz_timezone * tz)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
dump_country(int cfd,const struct tz_continent * cont,struct tz_country * country)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
dump_continent(int root,struct tz_continent * cont)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
main(int argc,char * argv[])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