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.6 2006/01/12 13:43:11 corecode 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'",
201 /* Now skip past the three-letter and numeric codes. */
202 name
= strsep(&s
, "\t"); /* 3-let */
203 if (name
== 0 || strlen(name
) != 3)
204 errx(1, _PATH_ISO3166
":%d: invalid format", lineno
);
205 name
= strsep(&s
, "\t"); /* numeric */
206 if (name
== 0 || strlen(name
) != 3)
207 errx(1, _PATH_ISO3166
":%d: invalid format", lineno
);
211 cp
= &countries
[CODE2INT(t
)];
213 errx(1, _PATH_ISO3166
214 ":%d: country code `%s' multiply defined: %s",
215 lineno
, t
, cp
->name
);
216 cp
->name
= strdup(name
);
217 if (cp
->name
== NULL
)
218 errx(1, "malloc failed");
221 errx(1, "malloc failed");
228 add_zone_to_country(int lineno
, const char *tlc
, const char *descr
,
229 const char *file
, struct continent
*cont
)
234 if (tlc
[0] < 'A' || tlc
[0] > 'Z' || tlc
[1] < 'A' || tlc
[1] > 'Z')
235 errx(1, _PATH_ZONETAB
":%d: country code `%s' invalid",
238 cp
= &countries
[CODE2INT(tlc
)];
240 errx(1, _PATH_ZONETAB
":%d: country code `%s' unknown",
245 errx(1, _PATH_ZONETAB
246 ":%d: conflicting zone definition", lineno
);
248 zp
= malloc(sizeof *zp
);
250 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp
);
253 TAILQ_INIT(&cp
->zones
);
255 zp
->descr
= strdup(descr
);
256 if (zp
->descr
== NULL
)
257 errx(1, "malloc failed");
258 zp
->filename
= strdup(file
);
259 if (zp
->filename
== NULL
)
260 errx(1, "malloc failed");
261 zp
->continent
= cont
;
262 TAILQ_INSERT_TAIL(&cp
->zones
, zp
, link
);
266 errx(1, _PATH_ZONETAB
267 ":%d: zone must have description", lineno
);
269 errx(1, _PATH_ZONETAB
270 ":%d: zone multiply defined", lineno
);
272 cp
->filename
= strdup(file
);
273 if (cp
->filename
== NULL
)
274 errx(1, "malloc failed");
275 cp
->continent
= cont
;
280 * This comparison function intentionally sorts all of the null-named
281 * ``countries''---i.e., the codes that don't correspond to a real
282 * country---to the end. Everything else is lexical by country name.
285 compare_countries(const void *xa
, const void *xb
)
287 const struct country
*a
= xa
, *b
= xb
;
289 if (a
->name
== 0 && b
->name
== 0)
291 if (a
->name
== 0 && b
->name
!= 0)
296 return strcmp(a
->name
, b
->name
);
300 * This must be done AFTER all zone descriptions are read, since it breaks
306 qsort(countries
, NCOUNTRIES
, sizeof countries
[0], compare_countries
);
316 char *tlc
, *coord
, *file
, *descr
, *p
;
318 struct continent
*cont
;
320 fp
= fopen(_PATH_ZONETAB
, "r");
322 err(1, _PATH_ZONETAB
);
325 while ((line
= fgetln(fp
, &len
)) != 0) {
327 if (line
[len
- 1] != '\n')
328 errx(1, _PATH_ZONETAB
":%d: invalid format", lineno
);
329 line
[len
- 1] = '\0';
333 tlc
= strsep(&line
, "\t");
334 if (strlen(tlc
) != 2)
335 errx(1, _PATH_ZONETAB
":%d: invalid country code `%s'",
337 coord
= strsep(&line
, "\t");
338 file
= strsep(&line
, "\t");
339 p
= strchr(file
, '/');
341 errx(1, _PATH_ZONETAB
":%d: invalid zone name `%s'",
344 strncat(contbuf
, file
, p
- file
);
345 cont
= find_continent(contbuf
);
347 errx(1, _PATH_ZONETAB
":%d: invalid region `%s'",
350 descr
= (line
&& *line
) ? line
: 0;
352 add_zone_to_country(lineno
, tlc
, descr
, file
, cont
);
361 struct zone
*zp
, *zp2
;
362 struct continent
*cont
;
367 * First, count up all the countries in each continent/ocean.
368 * Be careful to count those countries which have multiple zones
369 * only once for each. NB: some countries are in multiple
372 for (cp
= countries
; cp
->name
; cp
++) {
375 if (cp
->nzones
< 0) {
376 cp
->continent
->nitems
++;
378 for (zp
= cp
->zones
.tqh_first
; zp
;
379 zp
= zp
->link
.tqe_next
) {
380 cont
= zp
->continent
;
381 for (zp2
= cp
->zones
.tqh_first
;
382 zp2
->continent
!= cont
;
383 zp2
= zp2
->link
.tqe_next
)
386 zp
->continent
->nitems
++;
392 * Now allocate memory for the country menus. We set
393 * nitems back to zero so that we can use it for counting
394 * again when we actually build the menus.
396 for (i
= 0; i
< NCONTINENTS
; i
++) {
397 continent_names
[i
].continent
->menu
=
398 malloc(sizeof(dialogMenuItem
) *
399 continent_names
[i
].continent
->nitems
);
400 if (continent_names
[i
].continent
->menu
== 0)
401 errx(1, "malloc for continent menu");
402 continent_names
[i
].continent
->nitems
= 0;
406 * Now that memory is allocated, create the menu items for
407 * each continent. For multiple-zone countries, also create
408 * the country's zone submenu.
410 for (cp
= countries
; cp
->name
; cp
++) {
413 if (cp
->nzones
< 0) {
414 dmi
= &cp
->continent
->menu
[cp
->continent
->nitems
];
415 memset(dmi
, 0, sizeof *dmi
);
416 asprintf(&dmi
->prompt
, "%d",
417 ++cp
->continent
->nitems
);
418 dmi
->title
= cp
->name
;
420 dmi
->fire
= set_zone_whole_country
;
424 cp
->submenu
= malloc(cp
->nzones
* sizeof *dmi
);
425 if (cp
->submenu
== 0)
426 errx(1, "malloc for submenu");
428 for (zp
= cp
->zones
.tqh_first
; zp
;
429 zp
= zp
->link
.tqe_next
) {
430 cont
= zp
->continent
;
431 dmi
= &cp
->submenu
[cp
->nzones
];
432 memset(dmi
, 0, sizeof *dmi
);
433 asprintf(&dmi
->prompt
, "%d",
435 dmi
->title
= zp
->descr
;
437 dmi
->fire
= set_zone_multi
;
441 for (zp2
= cp
->zones
.tqh_first
;
442 zp2
->continent
!= cont
;
443 zp2
= zp2
->link
.tqe_next
)
448 dmi
= &cont
->menu
[cont
->nitems
];
449 memset(dmi
, 0, sizeof *dmi
);
450 asprintf(&dmi
->prompt
, "%d", ++cont
->nitems
);
451 dmi
->title
= cp
->name
;
453 dmi
->fire
= set_zone_menu
;
462 set_zone_menu(dialogMenuItem
*dmi
)
466 struct country
*cp
= dmi
->data
;
469 snprintf(buf
, sizeof buf
, "%s Time Zones", cp
->name
);
470 menulen
= cp
->nzones
< 16 ? cp
->nzones
: 16;
471 rv
= dialog_menu(buf
, "Select a zone which observes the same time as "
472 "your locality.", -1, -1, menulen
, -cp
->nzones
,
473 cp
->submenu
, 0, 0, 0);
475 return DITEM_RECREATE
;
476 return DITEM_LEAVE_MENU
;
480 install_zone_file(const char *filename
)
489 if (lstat(_PATH_LOCALTIME
, &sb
) < 0)
490 /* Nothing there yet... */
492 else if(S_ISLNK(sb
.st_mode
))
499 asprintf(&msg
, "Copying %s to " _PATH_LOCALTIME
, filename
);
501 asprintf(&msg
, "Creating symbolic link " _PATH_LOCALTIME
510 fd1
= open(filename
, O_RDONLY
, 0);
512 asprintf(&msg
, "Could not open %s: %s",
513 filename
, strerror(errno
));
514 dialog_mesgbox("Error", msg
, 8, 72);
516 return DITEM_FAILURE
| DITEM_RECREATE
;
519 unlink(_PATH_LOCALTIME
);
520 fd2
= open(_PATH_LOCALTIME
,
521 O_CREAT
|O_EXCL
|O_WRONLY
,
522 S_IRUSR
|S_IRGRP
|S_IROTH
);
524 asprintf(&msg
, "Could not open "
525 _PATH_LOCALTIME
": %s",
527 dialog_mesgbox("Error", msg
, 8, 72);
529 return DITEM_FAILURE
| DITEM_RECREATE
;
532 while ((len
= read(fd1
, buf
, sizeof buf
)) > 0)
533 len
= write(fd2
, buf
, len
);
536 asprintf(&msg
, "Error copying %s to "
537 _PATH_LOCALTIME
": %s",
538 filename
, strerror(errno
));
539 dialog_mesgbox("Error", msg
, 8, 72);
541 /* Better to leave none than a corrupt one. */
542 unlink(_PATH_LOCALTIME
);
543 return DITEM_FAILURE
| DITEM_RECREATE
;
548 if (access(filename
, R_OK
) != 0) {
549 asprintf(&msg
, "Cannot access %s: %s",
550 filename
, strerror(errno
));
551 dialog_mesgbox("Error", msg
, 8, 72);
553 return DITEM_FAILURE
| DITEM_RECREATE
;
555 unlink(_PATH_LOCALTIME
);
556 if (symlink(filename
, _PATH_LOCALTIME
) < 0) {
557 asprintf(&msg
, "Cannot create symbolic link "
558 _PATH_LOCALTIME
" to %s: %s",
559 filename
, strerror(errno
));
560 dialog_mesgbox("Error", msg
, 8, 72);
562 return DITEM_FAILURE
| DITEM_RECREATE
;
569 asprintf(&msg
, "Copied timezone file from %s to "
570 _PATH_LOCALTIME
, filename
);
572 asprintf(&msg
, "Created symbolic link from " _PATH_LOCALTIME
575 dialog_mesgbox("Done", msg
, 8, 72);
578 return DITEM_LEAVE_MENU
;
582 confirm_zone(const char *filename
)
589 if (setenv("TZ", filename
, 1) == -1)
590 err(1, "setenv: cannot set TZ=%s", filename
);
594 asprintf(&msg
, "Does the abbreviation `%s' look reasonable?",
596 rv
= !dialog_yesno("Confirmation", msg
, 4, 72);
602 set_zone_multi(dialogMenuItem
*dmi
)
605 struct zone
*zp
= dmi
->data
;
608 if (!confirm_zone(zp
->filename
))
609 return DITEM_FAILURE
| DITEM_RECREATE
;
611 asprintf(&fn
, "%s/%s", _PATH_ZONEINFO
, zp
->filename
);
612 rv
= install_zone_file(fn
);
618 set_zone_whole_country(dialogMenuItem
*dmi
)
621 struct country
*cp
= dmi
->data
;
624 if (!confirm_zone(cp
->filename
))
625 return DITEM_FAILURE
| DITEM_RECREATE
;
627 asprintf(&fn
, "%s/%s", _PATH_ZONEINFO
, cp
->filename
);
628 rv
= install_zone_file(fn
);
636 fprintf(stderr
, "usage: tzsetup [-n]\n");
641 main(int argc
, char **argv
)
644 int (*dialog_utc
)(unsigned char *, unsigned char *, int, int);
646 dialog_utc
= dialog_noyes
;
648 while ((c
= getopt(argc
, argv
, "n")) != -1) {
659 if (argc
- optind
> 1)
662 /* Override the user-supplied umask. */
663 umask(S_IWGRP
|S_IWOTH
);
665 read_iso3166_table();
671 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
672 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
673 "or you don't know, please choose NO here!", 7, 72)) {
675 unlink(_PATH_WALL_CMOS_CLOCK
);
678 fd
= open(_PATH_WALL_CMOS_CLOCK
,
679 O_WRONLY
|O_CREAT
|O_TRUNC
,
680 S_IRUSR
|S_IRGRP
|S_IROTH
);
682 err(1, "create %s", _PATH_WALL_CMOS_CLOCK
);
686 dialog_clear_norefresh();
687 if (optind
== argc
- 1) {
689 asprintf(&msg
, "\nUse the default `%s' zone?", argv
[optind
]);
690 if (!dialog_yesno("Default timezone provided", msg
, 7, 72)) {
691 install_zone_file(argv
[optind
]);
697 dialog_clear_norefresh();
699 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
700 NCONTINENTS
, -NCONTINENTS
, continents
, 0, NULL
, NULL
);