Merge from vendor branch BZIP:
[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.7 2008/03/02 14:28:37 swildner 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                 name = s;
202
203                 cp = &countries[CODE2INT(t)];
204                 if (cp->name)
205                         errx(1, _PATH_ISO3166 
206                              ":%d: country code `%s' multiply defined: %s",
207                              lineno, t, cp->name);
208                 cp->name = strdup(name);
209                 if (cp->name == NULL)
210                         errx(1, "malloc failed");
211                 cp->tlc = strdup(t);
212                 if (cp->tlc == NULL)
213                         errx(1, "malloc failed");
214         }
215
216         fclose(fp);
217 }
218
219 static void
220 add_zone_to_country(int lineno, const char *tlc, const char *descr,
221                     const char *file, struct continent *cont)
222 {
223         struct zone *zp;
224         struct country *cp;
225
226         if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
227                 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
228                      lineno, tlc);
229         
230         cp = &countries[CODE2INT(tlc)];
231         if (cp->name == 0)
232                 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
233                      lineno, tlc);
234
235         if (descr) {
236                 if (cp->nzones < 0)
237                         errx(1, _PATH_ZONETAB 
238                              ":%d: conflicting zone definition", lineno);
239
240                 zp = malloc(sizeof *zp);
241                 if (zp == 0)
242                         errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
243                 
244                 if (cp->nzones == 0)
245                         TAILQ_INIT(&cp->zones);
246
247                 zp->descr = strdup(descr);
248                 if (zp->descr == NULL)
249                         errx(1, "malloc failed");
250                 zp->filename = strdup(file);
251                 if (zp->filename == NULL)
252                         errx(1, "malloc failed");
253                 zp->continent = cont;
254                 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
255                 cp->nzones++;
256         } else {
257                 if (cp->nzones > 0)
258                         errx(1, _PATH_ZONETAB 
259                              ":%d: zone must have description", lineno);
260                 if (cp->nzones < 0)
261                         errx(1, _PATH_ZONETAB
262                              ":%d: zone multiply defined", lineno);
263                 cp->nzones = -1;
264                 cp->filename = strdup(file);
265                 if (cp->filename == NULL)
266                         errx(1, "malloc failed");
267                 cp->continent = cont;
268         }
269 }
270
271 /*
272  * This comparison function intentionally sorts all of the null-named
273  * ``countries''---i.e., the codes that don't correspond to a real
274  * country---to the end.  Everything else is lexical by country name.
275  */
276 static int
277 compare_countries(const void *xa, const void *xb)
278 {
279         const struct country *a = xa, *b = xb;
280
281         if (a->name == 0 && b->name == 0)
282                 return 0;
283         if (a->name == 0 && b->name != 0)
284                 return 1;
285         if (b->name == 0)
286                 return -1;
287
288         return strcmp(a->name, b->name);
289 }
290
291 /*
292  * This must be done AFTER all zone descriptions are read, since it breaks
293  * CODE2INT().
294  */
295 static void
296 sort_countries(void)
297 {
298         qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
299 }
300
301 static void
302 read_zones(void)
303 {
304         FILE *fp;
305         char *line;
306         size_t len;
307         int lineno;
308         char *tlc, *coord, *file, *descr, *p;
309         char contbuf[16];
310         struct continent *cont;
311
312         fp = fopen(_PATH_ZONETAB, "r");
313         if (!fp)
314                 err(1, _PATH_ZONETAB);
315         lineno = 0;
316
317         while ((line = fgetln(fp, &len)) != 0) {
318                 lineno++;
319                 if (line[len - 1] != '\n')
320                         errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
321                 line[len - 1] = '\0';
322                 if (line[0] == '#')
323                         continue;
324
325                 tlc = strsep(&line, "\t");
326                 if (strlen(tlc) != 2)
327                         errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
328                              lineno, tlc);
329                 coord = strsep(&line, "\t");
330                 file = strsep(&line, "\t");
331                 p = strchr(file, '/');
332                 if (p == 0)
333                         errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
334                              lineno, file);
335                 contbuf[0] = '\0';
336                 strncat(contbuf, file, p - file);
337                 cont = find_continent(contbuf);
338                 if (!cont)
339                         errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
340                              lineno, contbuf);
341
342                 descr = (line && *line) ? line : 0;
343
344                 add_zone_to_country(lineno, tlc, descr, file, cont);
345         }
346         fclose(fp);
347 }
348
349 static void
350 make_menus(void)
351 {
352         struct country *cp;
353         struct zone *zp, *zp2;
354         struct continent *cont;
355         dialogMenuItem *dmi;
356         int i;
357
358         /*
359          * First, count up all the countries in each continent/ocean.
360          * Be careful to count those countries which have multiple zones
361          * only once for each.  NB: some countries are in multiple
362          * continents/oceans.
363          */
364         for (cp = countries; cp->name; cp++) {
365                 if (cp->nzones == 0)
366                         continue;
367                 if (cp->nzones < 0) {
368                         cp->continent->nitems++;
369                 } else {
370                         for (zp = cp->zones.tqh_first; zp; 
371                              zp = zp->link.tqe_next) {
372                                 cont = zp->continent;
373                                 for (zp2 = cp->zones.tqh_first;
374                                      zp2->continent != cont;
375                                      zp2 = zp2->link.tqe_next)
376                                         ;
377                                 if (zp2 == zp)
378                                         zp->continent->nitems++;
379                         }
380                 }
381         }
382
383         /*
384          * Now allocate memory for the country menus.  We set
385          * nitems back to zero so that we can use it for counting
386          * again when we actually build the menus.
387          */
388         for (i = 0; i < NCONTINENTS; i++) {
389                 continent_names[i].continent->menu =
390                         malloc(sizeof(dialogMenuItem) *
391                                continent_names[i].continent->nitems);
392                 if (continent_names[i].continent->menu == 0)
393                         errx(1, "malloc for continent menu");
394                 continent_names[i].continent->nitems = 0;
395         }
396
397         /*
398          * Now that memory is allocated, create the menu items for
399          * each continent.  For multiple-zone countries, also create
400          * the country's zone submenu.
401          */
402         for (cp = countries; cp->name; cp++) {
403                 if (cp->nzones == 0)
404                         continue;
405                 if (cp->nzones < 0) {
406                         dmi = &cp->continent->menu[cp->continent->nitems];
407                         memset(dmi, 0, sizeof *dmi);
408                         asprintf(&dmi->prompt, "%d", 
409                                  ++cp->continent->nitems);
410                         dmi->title = cp->name;
411                         dmi->checked = 0;
412                         dmi->fire = set_zone_whole_country;
413                         dmi->selected = 0;
414                         dmi->data = cp;
415                 } else {
416                         cp->submenu = malloc(cp->nzones * sizeof *dmi);
417                         if (cp->submenu == 0)
418                                 errx(1, "malloc for submenu");
419                         cp->nzones = 0;
420                         for (zp = cp->zones.tqh_first; zp; 
421                              zp = zp->link.tqe_next) {
422                                 cont = zp->continent;
423                                 dmi = &cp->submenu[cp->nzones];
424                                 memset(dmi, 0, sizeof *dmi);
425                                 asprintf(&dmi->prompt, "%d",
426                                          ++cp->nzones);
427                                 dmi->title = zp->descr;
428                                 dmi->checked = 0;
429                                 dmi->fire = set_zone_multi;
430                                 dmi->selected = 0;
431                                 dmi->data = zp;
432
433                                 for (zp2 = cp->zones.tqh_first;
434                                      zp2->continent != cont;
435                                      zp2 = zp2->link.tqe_next)
436                                         ;
437                                 if (zp2 != zp)
438                                         continue;
439
440                                 dmi = &cont->menu[cont->nitems];
441                                 memset(dmi, 0, sizeof *dmi);
442                                 asprintf(&dmi->prompt, "%d", ++cont->nitems);
443                                 dmi->title = cp->name;
444                                 dmi->checked = 0;
445                                 dmi->fire = set_zone_menu;
446                                 dmi->selected = 0;
447                                 dmi->data = cp;
448                         }
449                 }
450         }
451 }
452
453 static int
454 set_zone_menu(dialogMenuItem *dmi)
455 {
456         int rv;
457         char buf[256];
458         struct country *cp = dmi->data;
459         int menulen;
460
461         snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
462         menulen = cp->nzones < 16 ? cp->nzones : 16;
463         rv = dialog_menu(buf, "Select a zone which observes the same time as "
464                          "your locality.", -1, -1, menulen, -cp->nzones,
465                          cp->submenu, 0, 0, 0);
466         if (rv != 0)
467                 return DITEM_RECREATE;
468         return DITEM_LEAVE_MENU;
469 }
470
471 static int
472 install_zone_file(const char *filename)
473 {
474         struct stat sb;
475         int fd1, fd2;
476         int copymode;
477         char *msg;
478         ssize_t len;
479         char buf[1024];
480
481         if (lstat(_PATH_LOCALTIME, &sb) < 0)
482                 /* Nothing there yet... */
483                 copymode = 1;
484         else if(S_ISLNK(sb.st_mode))
485                 copymode = 0;
486         else
487                 copymode = 1;
488
489 #ifdef VERBOSE
490         if (copymode)
491                 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
492         else
493                 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
494                          " to %s", filename);
495
496         dialog_notify(msg);
497         free(msg);
498 #endif
499
500         if (reallydoit) {
501                 if (copymode) {
502                         fd1 = open(filename, O_RDONLY, 0);
503                         if (fd1 < 0) {
504                                 asprintf(&msg, "Could not open %s: %s",
505                                          filename, strerror(errno));
506                                 dialog_mesgbox("Error", msg, 8, 72);
507                                 free(msg);
508                                 return DITEM_FAILURE | DITEM_RECREATE;
509                         }
510
511                         unlink(_PATH_LOCALTIME);
512                         fd2 = open(_PATH_LOCALTIME, 
513                                    O_CREAT|O_EXCL|O_WRONLY,
514                                    S_IRUSR|S_IRGRP|S_IROTH);
515                         if (fd2 < 0) {
516                                 asprintf(&msg, "Could not open "
517                                          _PATH_LOCALTIME ": %s", 
518                                          strerror(errno));
519                                 dialog_mesgbox("Error", msg, 8, 72);
520                                 free(msg);
521                                 return DITEM_FAILURE | DITEM_RECREATE;
522                         }
523
524                         while ((len = read(fd1, buf, sizeof buf)) > 0)
525                                 len = write(fd2, buf, len);
526
527                         if (len == -1) {
528                                 asprintf(&msg, "Error copying %s to "
529                                          _PATH_LOCALTIME ": %s",
530                                          filename, strerror(errno));
531                                 dialog_mesgbox("Error", msg, 8, 72);
532                                 free(msg);
533                                 /* Better to leave none than a corrupt one. */
534                                 unlink(_PATH_LOCALTIME);
535                                 return DITEM_FAILURE | DITEM_RECREATE;
536                         }
537                         close(fd1);
538                         close(fd2);
539                 } else {
540                         if (access(filename, R_OK) != 0) {
541                                 asprintf(&msg, "Cannot access %s: %s",
542                                          filename, strerror(errno));
543                                 dialog_mesgbox("Error", msg, 8, 72);
544                                 free(msg);
545                                 return DITEM_FAILURE | DITEM_RECREATE;
546                         }
547                         unlink(_PATH_LOCALTIME);
548                         if (symlink(filename, _PATH_LOCALTIME) < 0) {
549                                 asprintf(&msg, "Cannot create symbolic link "
550                                          _PATH_LOCALTIME " to %s: %s",
551                                          filename, strerror(errno));
552                                 dialog_mesgbox("Error", msg, 8, 72);
553                                 free(msg);
554                                 return DITEM_FAILURE | DITEM_RECREATE;
555                         }
556                 }
557         }
558
559 #ifdef VERBOSE
560         if (copymode)
561                 asprintf(&msg, "Copied timezone file from %s to " 
562                          _PATH_LOCALTIME, filename);
563         else
564                 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
565                          " to %s", filename);
566
567         dialog_mesgbox("Done", msg, 8, 72);
568         free(msg);
569 #endif
570         return DITEM_LEAVE_MENU;
571 }
572
573 static int
574 confirm_zone(const char *filename)
575 {
576         char *msg;
577         struct tm *tm;
578         time_t t = time(0);
579         int rv;
580         
581         if (setenv("TZ", filename, 1) == -1)
582                 err(1, "setenv: cannot set TZ=%s", filename);
583         tzset();
584         tm = localtime(&t);
585
586         asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
587                  tm->tm_zone);
588         rv = !dialog_yesno("Confirmation", msg, 4, 72);
589         free(msg);
590         return rv;
591 }
592
593 static int
594 set_zone_multi(dialogMenuItem *dmi)
595 {
596         char *fn;
597         struct zone *zp = dmi->data;
598         int rv;
599
600         if (!confirm_zone(zp->filename))
601                 return DITEM_FAILURE | DITEM_RECREATE;
602
603         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
604         rv = install_zone_file(fn);
605         free(fn);
606         return rv;
607 }
608
609 static int
610 set_zone_whole_country(dialogMenuItem *dmi)
611 {
612         char *fn;
613         struct country *cp = dmi->data;
614         int rv;
615
616         if (!confirm_zone(cp->filename))
617                 return DITEM_FAILURE | DITEM_RECREATE;
618
619         asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
620         rv = install_zone_file(fn);
621         free(fn);
622         return rv;
623 }
624
625 static void
626 usage(void)
627 {
628         fprintf(stderr, "usage: tzsetup [-n]\n");
629         exit(1);
630 }
631
632 int
633 main(int argc, char **argv)
634 {
635         int c, fd;
636         int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
637
638         dialog_utc = dialog_noyes;
639
640         while ((c = getopt(argc, argv, "n")) != -1) {
641                 switch(c) {
642                 case 'n':
643                         reallydoit = 0;
644                         break;
645
646                 default:
647                         usage();
648                 }
649         }
650
651         if (argc - optind > 1)
652                 usage();
653
654         /* Override the user-supplied umask. */
655         umask(S_IWGRP|S_IWOTH);
656
657         read_iso3166_table();
658         read_zones();
659         sort_countries();
660         make_menus();
661
662         init_dialog();
663         if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
664                         "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
665                         "or you don't know, please choose NO here!", 7, 72)) {
666                 if (reallydoit)
667                         unlink(_PATH_WALL_CMOS_CLOCK);
668         } else {
669                 if (reallydoit) {
670                         fd = open(_PATH_WALL_CMOS_CLOCK,
671                                   O_WRONLY|O_CREAT|O_TRUNC,
672                                   S_IRUSR|S_IRGRP|S_IROTH);
673                         if (fd < 0)
674                                 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
675                         close(fd);
676                 }
677         }
678         dialog_clear_norefresh();
679         if (optind == argc - 1) {
680                 char *msg;
681                 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
682                 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
683                         install_zone_file(argv[optind]);
684                         dialog_clear();
685                         end_dialog();
686                         return 0;
687                 }
688                 free(msg);
689                 dialog_clear_norefresh();
690         }
691         dialog_menu("Time Zone Selector", "Select a region", -1, -1, 
692                     NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
693
694         dialog_clear();
695         end_dialog();
696         return 0;
697 }
698