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