2 * Copyright 1996 Massachusetts Institute of Technology
4 * Permission to use, copy, modify, and distribute this software and
5 * its documentation for any purpose and without fee is hereby
6 * granted, provided that both the above copyright notice and this
7 * permission notice appear in all copies, that both the above
8 * copyright notice and this permission notice appear in all
9 * supporting documentation, and that the name of M.I.T. not be used
10 * in advertising or publicity pertaining to distribution of the
11 * software without specific, written prior permission. M.I.T. makes
12 * no representations about the suitability of this software for any
13 * purpose. It is provided "as is" without express or implied
16 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
17 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
20 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * $FreeBSD: src/usr.sbin/tzsetup/tzsetup.c,v 1.16.2.2 2002/03/06 06:17:41 obrien Exp $
30 * $DragonFly: src/usr.sbin/tzsetup/tzsetup.c,v 1.7 2008/03/02 14:28:37 swildner Exp $
34 * Second attempt at a `tzmenu' program, using the separate description
35 * files provided in newer tzdata releases.
38 #include <sys/types.h>
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
53 static int reallydoit
= 1;
55 static int continent_country_menu(dialogMenuItem
*);
56 static int set_zone_multi(dialogMenuItem
*);
57 static int set_zone_whole_country(dialogMenuItem
*);
58 static int set_zone_menu(dialogMenuItem
*);
67 static struct continent africa
, america
, antarctica
, arctic
, asia
, atlantic
;
68 static struct continent australia
, europe
, indian
, pacific
;
70 static struct continent_names
{
72 struct continent
*continent
;
73 } continent_names
[] = {
74 { "Africa", &africa
}, { "America", &america
},
75 { "Antarctica", &antarctica
}, { "Arctic", &arctic
},
77 { "Atlantic", &atlantic
}, { "Australia", &australia
},
78 { "Europe", &europe
}, { "Indian", &indian
}, { "Pacific", &pacific
}
81 static dialogMenuItem continents
[] = {
82 { "1", "Africa", 0, continent_country_menu
, 0, &africa
},
83 { "2", "America -- North and South", 0, continent_country_menu
, 0,
85 { "3", "Antarctica", 0, continent_country_menu
, 0, &antarctica
},
86 { "4", "Arctic Ocean", 0, continent_country_menu
, 0, &arctic
},
87 { "5", "Asia", 0, continent_country_menu
, 0, &asia
},
88 { "6", "Atlantic Ocean", 0, continent_country_menu
, 0, &atlantic
},
89 { "7", "Australia", 0, continent_country_menu
, 0, &australia
},
90 { "8", "Europe", 0, continent_country_menu
, 0, &europe
},
91 { "9", "Indian Ocean", 0, continent_country_menu
, 0, &indian
},
92 { "0", "Pacific Ocean", 0, continent_country_menu
, 0, &pacific
}
94 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
95 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
98 continent_country_menu(dialogMenuItem
*continent
)
101 struct continent
*contp
= continent
->data
;
103 int isocean
= OCEANP(continent
- continents
);
106 /* Short cut -- if there's only one country, don't post a menu. */
107 if (contp
->nitems
== 1) {
108 return set_zone_menu(&contp
->menu
[0]);
111 /* It's amazing how much good grammar really matters... */
113 snprintf(title
, sizeof title
, "Countries in %s",
116 snprintf(title
, sizeof title
, "Islands and groups in the %s",
119 menulen
= contp
->nitems
< 16 ? contp
->nitems
: 16;
120 rv
= dialog_menu(title
, (isocean
? "Select an island or group"
121 : "Select a country"), -1, -1, menulen
,
122 -contp
->nitems
, contp
->menu
, 0, &contp
->ch
,
125 return DITEM_LEAVE_MENU
;
126 return DITEM_RECREATE
;
129 static struct continent
*
130 find_continent(const char *name
)
134 for (i
= 0; i
< NCONTINENTS
; i
++) {
135 if (strcmp(name
, continent_names
[i
].name
) == 0)
136 return continent_names
[i
].continent
;
145 char *filename
; /* use iff nzones < 0 */
146 struct continent
*continent
; /* use iff nzones < 0 */
147 TAILQ_HEAD(, zone
) zones
; /* use iff nzones > 0 */
148 dialogMenuItem
*submenu
; /* use iff nzones > 0 */
152 TAILQ_ENTRY(zone
) link
;
155 struct continent
*continent
;
159 * This is the easiest organization... we use ISO 3166 country codes,
160 * of the two-letter variety, so we just size this array to suit.
161 * Beats worrying about dynamic allocation.
163 #define NCOUNTRIES (26*26)
164 static struct country countries
[NCOUNTRIES
];
165 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
168 * Read the ISO 3166 country code database in _PATH_ISO3166
169 * (/usr/share/misc/iso3166). On error, exit via err(3).
172 read_iso3166_table(void)
180 fp
= fopen(_PATH_ISO3166
, "r");
182 err(1, _PATH_ISO3166
);
185 while ((s
= fgetln(fp
, &len
)) != 0) {
187 if (s
[len
- 1] != '\n')
188 errx(1, _PATH_ISO3166
":%d: invalid format", lineno
);
190 if (s
[0] == '#' || strspn(s
, " \t") == len
- 1)
193 /* Isolate the two-letter code. */
194 t
= strsep(&s
, "\t");
195 if (t
== 0 || strlen(t
) != 2)
196 errx(1, _PATH_ISO3166
":%d: invalid format", lineno
);
197 if (t
[0] < 'A' || t
[0] > 'Z' || t
[1] < 'A' || t
[1] > 'Z')
198 errx(1, _PATH_ISO3166
":%d: invalid code `%s'",
203 cp
= &countries
[CODE2INT(t
)];
205 errx(1, _PATH_ISO3166
206 ":%d: country code `%s' multiply defined: %s",
207 lineno
, t
, cp
->name
);
208 cp
->name
= strdup(name
);
209 if (cp
->name
== NULL
)
210 errx(1, "malloc failed");
213 errx(1, "malloc failed");
220 add_zone_to_country(int lineno
, const char *tlc
, const char *descr
,
221 const char *file
, struct continent
*cont
)
226 if (tlc
[0] < 'A' || tlc
[0] > 'Z' || tlc
[1] < 'A' || tlc
[1] > 'Z')
227 errx(1, _PATH_ZONETAB
":%d: country code `%s' invalid",
230 cp
= &countries
[CODE2INT(tlc
)];
232 errx(1, _PATH_ZONETAB
":%d: country code `%s' unknown",
237 errx(1, _PATH_ZONETAB
238 ":%d: conflicting zone definition", lineno
);
240 zp
= malloc(sizeof *zp
);
242 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp
);
245 TAILQ_INIT(&cp
->zones
);
247 zp
->descr
= strdup(descr
);
248 if (zp
->descr
== NULL
)
249 errx(1, "malloc failed");
250 zp
->filename
= strdup(file
);
251 if (zp
->filename
== NULL
)
252 errx(1, "malloc failed");
253 zp
->continent
= cont
;
254 TAILQ_INSERT_TAIL(&cp
->zones
, zp
, link
);
258 errx(1, _PATH_ZONETAB
259 ":%d: zone must have description", lineno
);
261 errx(1, _PATH_ZONETAB
262 ":%d: zone multiply defined", lineno
);
264 cp
->filename
= strdup(file
);
265 if (cp
->filename
== NULL
)
266 errx(1, "malloc failed");
267 cp
->continent
= cont
;
272 * This comparison function intentionally sorts all of the null-named
273 * ``countries''---i.e., the codes that don't correspond to a real
274 * country---to the end. Everything else is lexical by country name.
277 compare_countries(const void *xa
, const void *xb
)
279 const struct country
*a
= xa
, *b
= xb
;
281 if (a
->name
== 0 && b
->name
== 0)
283 if (a
->name
== 0 && b
->name
!= 0)
288 return strcmp(a
->name
, b
->name
);
292 * This must be done AFTER all zone descriptions are read, since it breaks
298 qsort(countries
, NCOUNTRIES
, sizeof countries
[0], compare_countries
);
308 char *tlc
, *coord
, *file
, *descr
, *p
;
310 struct continent
*cont
;
312 fp
= fopen(_PATH_ZONETAB
, "r");
314 err(1, _PATH_ZONETAB
);
317 while ((line
= fgetln(fp
, &len
)) != 0) {
319 if (line
[len
- 1] != '\n')
320 errx(1, _PATH_ZONETAB
":%d: invalid format", lineno
);
321 line
[len
- 1] = '\0';
325 tlc
= strsep(&line
, "\t");
326 if (strlen(tlc
) != 2)
327 errx(1, _PATH_ZONETAB
":%d: invalid country code `%s'",
329 coord
= strsep(&line
, "\t");
330 file
= strsep(&line
, "\t");
331 p
= strchr(file
, '/');
333 errx(1, _PATH_ZONETAB
":%d: invalid zone name `%s'",
336 strncat(contbuf
, file
, p
- file
);
337 cont
= find_continent(contbuf
);
339 errx(1, _PATH_ZONETAB
":%d: invalid region `%s'",
342 descr
= (line
&& *line
) ? line
: 0;
344 add_zone_to_country(lineno
, tlc
, descr
, file
, cont
);
353 struct zone
*zp
, *zp2
;
354 struct continent
*cont
;
359 * First, count up all the countries in each continent/ocean.
360 * Be careful to count those countries which have multiple zones
361 * only once for each. NB: some countries are in multiple
364 for (cp
= countries
; cp
->name
; cp
++) {
367 if (cp
->nzones
< 0) {
368 cp
->continent
->nitems
++;
370 for (zp
= cp
->zones
.tqh_first
; zp
;
371 zp
= zp
->link
.tqe_next
) {
372 cont
= zp
->continent
;
373 for (zp2
= cp
->zones
.tqh_first
;
374 zp2
->continent
!= cont
;
375 zp2
= zp2
->link
.tqe_next
)
378 zp
->continent
->nitems
++;
384 * Now allocate memory for the country menus. We set
385 * nitems back to zero so that we can use it for counting
386 * again when we actually build the menus.
388 for (i
= 0; i
< NCONTINENTS
; i
++) {
389 continent_names
[i
].continent
->menu
=
390 malloc(sizeof(dialogMenuItem
) *
391 continent_names
[i
].continent
->nitems
);
392 if (continent_names
[i
].continent
->menu
== 0)
393 errx(1, "malloc for continent menu");
394 continent_names
[i
].continent
->nitems
= 0;
398 * Now that memory is allocated, create the menu items for
399 * each continent. For multiple-zone countries, also create
400 * the country's zone submenu.
402 for (cp
= countries
; cp
->name
; cp
++) {
405 if (cp
->nzones
< 0) {
406 dmi
= &cp
->continent
->menu
[cp
->continent
->nitems
];
407 memset(dmi
, 0, sizeof *dmi
);
408 asprintf(&dmi
->prompt
, "%d",
409 ++cp
->continent
->nitems
);
410 dmi
->title
= cp
->name
;
412 dmi
->fire
= set_zone_whole_country
;
416 cp
->submenu
= malloc(cp
->nzones
* sizeof *dmi
);
417 if (cp
->submenu
== 0)
418 errx(1, "malloc for submenu");
420 for (zp
= cp
->zones
.tqh_first
; zp
;
421 zp
= zp
->link
.tqe_next
) {
422 cont
= zp
->continent
;
423 dmi
= &cp
->submenu
[cp
->nzones
];
424 memset(dmi
, 0, sizeof *dmi
);
425 asprintf(&dmi
->prompt
, "%d",
427 dmi
->title
= zp
->descr
;
429 dmi
->fire
= set_zone_multi
;
433 for (zp2
= cp
->zones
.tqh_first
;
434 zp2
->continent
!= cont
;
435 zp2
= zp2
->link
.tqe_next
)
440 dmi
= &cont
->menu
[cont
->nitems
];
441 memset(dmi
, 0, sizeof *dmi
);
442 asprintf(&dmi
->prompt
, "%d", ++cont
->nitems
);
443 dmi
->title
= cp
->name
;
445 dmi
->fire
= set_zone_menu
;
454 set_zone_menu(dialogMenuItem
*dmi
)
458 struct country
*cp
= dmi
->data
;
461 snprintf(buf
, sizeof buf
, "%s Time Zones", cp
->name
);
462 menulen
= cp
->nzones
< 16 ? cp
->nzones
: 16;
463 rv
= dialog_menu(buf
, "Select a zone which observes the same time as "
464 "your locality.", -1, -1, menulen
, -cp
->nzones
,
465 cp
->submenu
, 0, 0, 0);
467 return DITEM_RECREATE
;
468 return DITEM_LEAVE_MENU
;
472 install_zone_file(const char *filename
)
481 if (lstat(_PATH_LOCALTIME
, &sb
) < 0)
482 /* Nothing there yet... */
484 else if(S_ISLNK(sb
.st_mode
))
491 asprintf(&msg
, "Copying %s to " _PATH_LOCALTIME
, filename
);
493 asprintf(&msg
, "Creating symbolic link " _PATH_LOCALTIME
502 fd1
= open(filename
, O_RDONLY
, 0);
504 asprintf(&msg
, "Could not open %s: %s",
505 filename
, strerror(errno
));
506 dialog_mesgbox("Error", msg
, 8, 72);
508 return DITEM_FAILURE
| DITEM_RECREATE
;
511 unlink(_PATH_LOCALTIME
);
512 fd2
= open(_PATH_LOCALTIME
,
513 O_CREAT
|O_EXCL
|O_WRONLY
,
514 S_IRUSR
|S_IRGRP
|S_IROTH
);
516 asprintf(&msg
, "Could not open "
517 _PATH_LOCALTIME
": %s",
519 dialog_mesgbox("Error", msg
, 8, 72);
521 return DITEM_FAILURE
| DITEM_RECREATE
;
524 while ((len
= read(fd1
, buf
, sizeof buf
)) > 0)
525 len
= write(fd2
, buf
, len
);
528 asprintf(&msg
, "Error copying %s to "
529 _PATH_LOCALTIME
": %s",
530 filename
, strerror(errno
));
531 dialog_mesgbox("Error", msg
, 8, 72);
533 /* Better to leave none than a corrupt one. */
534 unlink(_PATH_LOCALTIME
);
535 return DITEM_FAILURE
| DITEM_RECREATE
;
540 if (access(filename
, R_OK
) != 0) {
541 asprintf(&msg
, "Cannot access %s: %s",
542 filename
, strerror(errno
));
543 dialog_mesgbox("Error", msg
, 8, 72);
545 return DITEM_FAILURE
| DITEM_RECREATE
;
547 unlink(_PATH_LOCALTIME
);
548 if (symlink(filename
, _PATH_LOCALTIME
) < 0) {
549 asprintf(&msg
, "Cannot create symbolic link "
550 _PATH_LOCALTIME
" to %s: %s",
551 filename
, strerror(errno
));
552 dialog_mesgbox("Error", msg
, 8, 72);
554 return DITEM_FAILURE
| DITEM_RECREATE
;
561 asprintf(&msg
, "Copied timezone file from %s to "
562 _PATH_LOCALTIME
, filename
);
564 asprintf(&msg
, "Created symbolic link from " _PATH_LOCALTIME
567 dialog_mesgbox("Done", msg
, 8, 72);
570 return DITEM_LEAVE_MENU
;
574 confirm_zone(const char *filename
)
581 if (setenv("TZ", filename
, 1) == -1)
582 err(1, "setenv: cannot set TZ=%s", filename
);
586 asprintf(&msg
, "Does the abbreviation `%s' look reasonable?",
588 rv
= !dialog_yesno("Confirmation", msg
, 4, 72);
594 set_zone_multi(dialogMenuItem
*dmi
)
597 struct zone
*zp
= dmi
->data
;
600 if (!confirm_zone(zp
->filename
))
601 return DITEM_FAILURE
| DITEM_RECREATE
;
603 asprintf(&fn
, "%s/%s", _PATH_ZONEINFO
, zp
->filename
);
604 rv
= install_zone_file(fn
);
610 set_zone_whole_country(dialogMenuItem
*dmi
)
613 struct country
*cp
= dmi
->data
;
616 if (!confirm_zone(cp
->filename
))
617 return DITEM_FAILURE
| DITEM_RECREATE
;
619 asprintf(&fn
, "%s/%s", _PATH_ZONEINFO
, cp
->filename
);
620 rv
= install_zone_file(fn
);
628 fprintf(stderr
, "usage: tzsetup [-n]\n");
633 main(int argc
, char **argv
)
636 int (*dialog_utc
)(unsigned char *, unsigned char *, int, int);
638 dialog_utc
= dialog_noyes
;
640 while ((c
= getopt(argc
, argv
, "n")) != -1) {
651 if (argc
- optind
> 1)
654 /* Override the user-supplied umask. */
655 umask(S_IWGRP
|S_IWOTH
);
657 read_iso3166_table();
663 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
664 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
665 "or you don't know, please choose NO here!", 7, 72)) {
667 unlink(_PATH_WALL_CMOS_CLOCK
);
670 fd
= open(_PATH_WALL_CMOS_CLOCK
,
671 O_WRONLY
|O_CREAT
|O_TRUNC
,
672 S_IRUSR
|S_IRGRP
|S_IROTH
);
674 err(1, "create %s", _PATH_WALL_CMOS_CLOCK
);
678 dialog_clear_norefresh();
679 if (optind
== argc
- 1) {
681 asprintf(&msg
, "\nUse the default `%s' zone?", argv
[optind
]);
682 if (!dialog_yesno("Default timezone provided", msg
, 7, 72)) {
683 install_zone_file(argv
[optind
]);
689 dialog_clear_norefresh();
691 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
692 NCONTINENTS
, -NCONTINENTS
, continents
, 0, NULL
, NULL
);