Merge from vendor branch LIBSTDC++:
[dragonfly.git] / usr.sbin / tzsetup / tzsetup.c
1 /*
2  * Copyright 1996 Massachusetts Institute of Technology
3  *
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
14  * warranty.
15  * 
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
27  * SUCH DAMAGE.
28  *
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.2 2003/06/17 04:30:03 dillon Exp $
31  */
32
33 /*
34  * Second attempt at a `tzmenu' program, using the separate description
35  * files provided in newer tzdata releases.
36  */
37
38 #include <sys/types.h>
39 #include <dialog.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
49 #include <sys/stat.h>
50
51 #include "paths.h"
52
53 static int reallydoit = 1;
54
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 *);
59
60 struct continent {
61         dialogMenuItem *menu;
62         int nitems;
63         int ch;
64         int sc;
65 };
66
67 static struct continent africa, america, antarctica, arctic, asia, atlantic;
68 static struct continent australia, europe, indian, pacific;
69
70 static struct continent_names {
71         char *name;
72         struct continent *continent;
73 } continent_names[] = {
74         { "Africa", &africa }, { "America", &america },
75         { "Antarctica", &antarctica }, { "Arctic", &arctic }, 
76         { "Asia", &asia },
77         { "Atlantic", &atlantic }, { "Australia", &australia },
78         { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
79 };
80
81 static dialogMenuItem continents[] = {
82         { "1", "Africa", 0, continent_country_menu, 0, &africa },
83         { "2", "America -- North and South", 0, continent_country_menu, 0, 
84                   &america },
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 }
93 };
94 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
95 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
96
97 static int
98 continent_country_menu(dialogMenuItem *continent)
99 {
100         int rv;
101         struct continent *contp = continent->data;
102         char title[256];
103         int isocean = OCEANP(continent - continents);
104         int menulen;
105
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]);
109         }
110
111         /* It's amazing how much good grammar really matters... */
112         if (!isocean)
113                 snprintf(title, sizeof title, "Countries in %s", 
114                          continent->title);
115         else 
116                 snprintf(title, sizeof title, "Islands and groups in the %s",
117                          continent->title);
118
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,
123                          &contp->sc);
124         if (rv == 0)
125                 return DITEM_LEAVE_MENU;
126         return DITEM_RECREATE;
127 }
128
129 static struct continent *
130 find_continent(const char *name)
131 {
132         int i;
133
134         for (i = 0; i < NCONTINENTS; i++) {
135                 if (strcmp(name, continent_names[i].name) == 0)
136                         return continent_names[i].continent;
137         }
138         return 0;
139 }
140
141 struct country {
142         char *name;
143         char *tlc;
144         int nzones;
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 */
149 };
150
151 struct zone {
152         TAILQ_ENTRY(zone) link;
153         char *descr;
154         char *filename;
155         struct continent *continent;
156 };
157
158 /*
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.
162  */
163 #define NCOUNTRIES      (26*26)
164 static struct country countries[NCOUNTRIES];
165 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
166
167 /*
168  * Read the ISO 3166 country code database in _PATH_ISO3166
169  * (/usr/share/misc/iso3166).  On error, exit via err(3).
170  */
171 static void
172 read_iso3166_table(void)
173 {
174         FILE *fp;
175         char *s, *t, *name;
176         size_t len;
177         int lineno;
178         struct country *cp;
179
180         fp = fopen(_PATH_ISO3166, "r");
181         if (!fp)
182                 err(1, _PATH_ISO3166);
183         lineno = 0;
184
185         while ((s = fgetln(fp, &len)) != 0) {
186                 lineno++;
187                 if (s[len - 1] != '\n')
188                         errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
189                 s[len - 1] = '\0';
190                 if (s[0] == '#' || strspn(s, " \t") == len - 1)
191                         continue;
192
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'",
199                              lineno, t);
200
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);
208
209                 name = s;
210
211                 cp = &countries[CODE2INT(t)];
212                 if (cp->name)
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");
219                 cp->tlc = strdup(t);
220                 if (cp->tlc == NULL)
221                         errx(1, "malloc failed");
222         }
223
224         fclose(fp);
225 }
226
227 static void
228 add_zone_to_country(int lineno, const char *tlc, const char *descr,
229                     const char *file, struct continent *cont)
230 {
231         struct zone *zp;
232         struct country *cp;
233
234         if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
235                 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
236                      lineno, tlc);
237         
238         cp = &countries[CODE2INT(tlc)];
239         if (cp->name == 0)
240                 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
241                      lineno, tlc);
242
243         if (descr) {
244                 if (cp->nzones < 0)
245                         errx(1, _PATH_ZONETAB 
246                              ":%d: conflicting zone definition", lineno);
247
248                 zp = malloc(sizeof *zp);
249                 if (zp == 0)
250                         errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
251                 
252                 if (cp->nzones == 0)
253                         TAILQ_INIT(&cp->zones);
254
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);
263                 cp->nzones++;
264         } else {
265                 if (cp->nzones > 0)
266                         errx(1, _PATH_ZONETAB 
267                              ":%d: zone must have description", lineno);
268                 if (cp->nzones < 0)
269                         errx(1, _PATH_ZONETAB
270                              ":%d: zone multiply defined", lineno);
271                 cp->nzones = -1;
272                 cp->filename = strdup(file);
273                 if (cp->filename == NULL)
274                         errx(1, "malloc failed");
275                 cp->continent = cont;
276         }
277 }
278
279 /*
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.
283  */
284 static int
285 compare_countries(const void *xa, const void *xb)
286 {
287         const struct country *a = xa, *b = xb;
288
289         if (a->name == 0 && b->name == 0)
290                 return 0;
291         if (a->name == 0 && b->name != 0)
292                 return 1;
293         if (b->name == 0)
294                 return -1;
295
296         return strcmp(a->name, b->name);
297 }
298
299 /*
300  * This must be done AFTER all zone descriptions are read, since it breaks
301  * CODE2INT().
302  */
303 static void
304 sort_countries(void)
305 {
306         qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
307 }
308
309 static void
310 read_zones(void)
311 {
312         FILE *fp;
313         char *line;
314         size_t len;
315         int lineno;
316         char *tlc, *coord, *file, *descr, *p;
317         char contbuf[16];
318         struct continent *cont;
319
320         fp = fopen(_PATH_ZONETAB, "r");
321         if (!fp)
322                 err(1, _PATH_ZONETAB);
323         lineno = 0;
324
325         while ((line = fgetln(fp, &len)) != 0) {
326                 lineno++;
327                 if (line[len - 1] != '\n')
328                         errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
329                 line[len - 1] = '\0';
330                 if (line[0] == '#')
331                         continue;
332
333                 tlc = strsep(&line, "\t");
334                 if (strlen(tlc) != 2)
335                         errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
336                              lineno, tlc);
337                 coord = strsep(&line, "\t");
338                 file = strsep(&line, "\t");
339                 p = strchr(file, '/');
340                 if (p == 0)
341                         errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
342                              lineno, file);
343                 contbuf[0] = '\0';
344                 strncat(contbuf, file, p - file);
345                 cont = find_continent(contbuf);
346                 if (!cont)
347                         errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
348                              lineno, contbuf);
349
350                 descr = (line && *line) ? line : 0;
351
352                 add_zone_to_country(lineno, tlc, descr, file, cont);
353         }
354         fclose(fp);
355 }
356
357 static void
358 make_menus(void)
359 {
360         struct country *cp;
361         struct zone *zp, *zp2;
362         struct continent *cont;
363         dialogMenuItem *dmi;
364         int i;
365
366         /*
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
370          * continents/oceans.
371          */
372         for (cp = countries; cp->name; cp++) {
373                 if (cp->nzones == 0)
374                         continue;
375                 if (cp->nzones < 0) {
376                         cp->continent->nitems++;
377                 } else {
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)
384                                         ;
385                                 if (zp2 == zp)
386                                         zp->continent->nitems++;
387                         }
388                 }
389         }
390
391         /*
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.
395          */
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;
403         }
404
405         /*
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.
409          */
410         for (cp = countries; cp->name; cp++) {
411                 if (cp->nzones == 0)
412                         continue;
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;
419                         dmi->checked = 0;
420                         dmi->fire = set_zone_whole_country;
421                         dmi->selected = 0;
422                         dmi->data = cp;
423                 } else {
424                         cp->submenu = malloc(cp->nzones * sizeof *dmi);
425                         if (cp->submenu == 0)
426                                 errx(1, "malloc for submenu");
427                         cp->nzones = 0;
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",
434                                          ++cp->nzones);
435                                 dmi->title = zp->descr;
436                                 dmi->checked = 0;
437                                 dmi->fire = set_zone_multi;
438                                 dmi->selected = 0;
439                                 dmi->data = zp;
440
441                                 for (zp2 = cp->zones.tqh_first;
442                                      zp2->continent != cont;
443                                      zp2 = zp2->link.tqe_next)
444                                         ;
445                                 if (zp2 != zp)
446                                         continue;
447
448                                 dmi = &cont->menu[cont->nitems];
449                                 memset(dmi, 0, sizeof *dmi);
450                                 asprintf(&dmi->prompt, "%d", ++cont->nitems);
451                                 dmi->title = cp->name;
452                                 dmi->checked = 0;
453                                 dmi->fire = set_zone_menu;
454                                 dmi->selected = 0;
455                                 dmi->data = cp;
456                         }
457                 }
458         }
459 }
460
461 static int
462 set_zone_menu(dialogMenuItem *dmi)
463 {
464         int rv;
465         char buf[256];
466         struct country *cp = dmi->data;
467         int menulen;
468
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);
474         if (rv != 0)
475                 return DITEM_RECREATE;
476         return DITEM_LEAVE_MENU;
477 }
478
479 static int
480 install_zone_file(const char *filename)
481 {
482         struct stat sb;
483         int fd1, fd2;
484         int copymode;
485         char *msg;
486         ssize_t len;
487         char buf[1024];
488
489         if (lstat(_PATH_LOCALTIME, &sb) < 0)
490                 /* Nothing there yet... */
491                 copymode = 1;
492         else if(S_ISLNK(sb.st_mode))
493                 copymode = 0;
494         else
495                 copymode = 1;
496
497 #ifdef VERBOSE
498         if (copymode)
499                 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
500         else
501                 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
502                          " to %s", filename);
503
504         dialog_notify(msg);
505         free(msg);
506 #endif
507
508         if (reallydoit) {
509                 if (copymode) {
510                         fd1 = open(filename, O_RDONLY, 0);
511                         if (fd1 < 0) {
512                                 asprintf(&msg, "Could not open %s: %s",
513                                          filename, strerror(errno));
514                                 dialog_mesgbox("Error", msg, 8, 72);
515                                 free(msg);
516                                 return DITEM_FAILURE | DITEM_RECREATE;
517                         }
518
519                         unlink(_PATH_LOCALTIME);
520                         fd2 = open(_PATH_LOCALTIME, 
521                                    O_CREAT|O_EXCL|O_WRONLY,
522                                    S_IRUSR|S_IRGRP|S_IROTH);
523                         if (fd2 < 0) {
524                                 asprintf(&msg, "Could not open "
525                                          _PATH_LOCALTIME ": %s", 
526                                          strerror(errno));
527                                 dialog_mesgbox("Error", msg, 8, 72);
528                                 free(msg);
529                                 return DITEM_FAILURE | DITEM_RECREATE;
530                         }
531
532                         while ((len = read(fd1, buf, sizeof buf)) > 0)
533                                 len = write(fd2, buf, len);
534
535                         if (len == -1) {
536                                 asprintf(&msg, "Error copying %s to "
537                                          _PATH_LOCALTIME ": %s",
538                                          filename, strerror(errno));
539                                 dialog_mesgbox("Error", msg, 8, 72);
540                                 free(msg);
541                                 /* Better to leave none than a corrupt one. */
542                                 unlink(_PATH_LOCALTIME);
543                                 return DITEM_FAILURE | DITEM_RECREATE;
544                         }
545                         close(fd1);
546                         close(fd2);
547                 } else {
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);
552                                 free(msg);
553                                 return DITEM_FAILURE | DITEM_RECREATE;
554                         }
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);
561                                 free(msg);
562                                 return DITEM_FAILURE | DITEM_RECREATE;
563                         }
564                 }
565         }
566
567 #ifdef VERBOSE
568         if (copymode)
569                 asprintf(&msg, "Copied timezone file from %s to " 
570                          _PATH_LOCALTIME, filename);
571         else
572                 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
573                          " to %s", filename);
574
575         dialog_mesgbox("Done", msg, 8, 72);
576         free(msg);
577 #endif
578         return DITEM_LEAVE_MENU;
579 }
580
581 static int
582 confirm_zone(const char *filename)
583 {
584         char *msg;
585         struct tm *tm;
586         time_t t = time(0);
587         int rv;
588         
589         setenv("TZ", filename, 1);
590         tzset();
591         tm = localtime(&t);
592
593         asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
594                  tm->tm_zone);
595         rv = !dialog_yesno("Confirmation", msg, 4, 72);
596         free(msg);
597         return rv;
598 }
599
600 static int
601 set_zone_multi(dialogMenuItem *dmi)
602 {
603         char *fn;
604         struct zone *zp = dmi->data;
605         int rv;
606
607         if (!confirm_zone(zp->filename))
608                 return DITEM_FAILURE | DITEM_RECREATE;
609
610         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
611         rv = install_zone_file(fn);
612         free(fn);
613         return rv;
614 }
615
616 static int
617 set_zone_whole_country(dialogMenuItem *dmi)
618 {
619         char *fn;
620         struct country *cp = dmi->data;
621         int rv;
622
623         if (!confirm_zone(cp->filename))
624                 return DITEM_FAILURE | DITEM_RECREATE;
625
626         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
627         rv = install_zone_file(fn);
628         free(fn);
629         return rv;
630 }
631
632 static void
633 usage()
634 {
635         fprintf(stderr, "usage: tzsetup [-n]\n");
636         exit(1);
637 }
638
639 int
640 main(int argc, char **argv)
641 {
642         int c, fd;
643         int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
644
645 #if defined(__alpha__)
646         dialog_utc = dialog_yesno;
647 #else
648         dialog_utc = dialog_noyes;
649 #endif
650
651         while ((c = getopt(argc, argv, "n")) != -1) {
652                 switch(c) {
653                 case 'n':
654                         reallydoit = 0;
655                         break;
656
657                 default:
658                         usage();
659                 }
660         }
661
662         if (argc - optind > 1)
663                 usage();
664
665         /* Override the user-supplied umask. */
666         (void)umask(S_IWGRP|S_IWOTH);
667
668         read_iso3166_table();
669         read_zones();
670         sort_countries();
671         make_menus();
672
673         init_dialog();
674         if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
675                         "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
676                         "or you don't know, please choose NO here!", 7, 72)) {
677                 if (reallydoit)
678                         unlink(_PATH_WALL_CMOS_CLOCK);
679         } else {
680                 if (reallydoit) {
681                         fd = open(_PATH_WALL_CMOS_CLOCK,
682                                   O_WRONLY|O_CREAT|O_TRUNC,
683                                   S_IRUSR|S_IRGRP|S_IROTH);
684                         if (fd < 0)
685                                 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
686                         close(fd);
687                 }
688         }
689         dialog_clear_norefresh();
690         if (optind == argc - 1) {
691                 char *msg;
692                 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
693                 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
694                         install_zone_file(argv[optind]);
695                         dialog_clear();
696                         end_dialog();
697                         return 0;
698                 }
699                 free(msg);
700                 dialog_clear_norefresh();
701         }
702         dialog_menu("Time Zone Selector", "Select a region", -1, -1, 
703                     NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
704
705         dialog_clear();
706         end_dialog();
707         return 0;
708 }
709