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