xref: /illumos-gate/usr/src/test/tz-tests/tests/zoneinfo_dump.c (revision 24a03f35583aede6d1aea859d90d76a3e6c2eb22)
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