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