Merge branch 'vendor/TCSH'
[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: head/usr.sbin/tzsetup/tzsetup.c 247780 2013-03-04 11:34:31Z dteske $
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 <err.h>
38 #include <errno.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <sys/fcntl.h>
46 #include <sys/param.h>
47 #include <sys/queue.h>
48 #include <sys/stat.h>
49
50 #include <dialog.h>
51
52 #define _PATH_ZONETAB           "/usr/share/zoneinfo/zone.tab"
53 #define _PATH_ISO3166           "/usr/share/misc/iso3166"
54 #define _PATH_ZONEINFO          "/usr/share/zoneinfo"
55 #define _PATH_LOCALTIME         "/etc/localtime"
56 #define _PATH_DB                "/var/db/zoneinfo"
57 #define _PATH_WALL_CMOS_CLOCK   "/etc/wall_cmos_clock"
58
59 #ifdef PATH_MAX
60 #define SILLY_BUFFER_SIZE       2*PATH_MAX
61 #else
62 #warning "Somebody needs to fix this to dynamically size this buffer."
63 #define SILLY_BUFFER_SIZE       2048
64 #endif
65
66 /* special return codes for `fire' actions */
67 #define DITEM_FAILURE           1
68
69 /* flags - returned in upper 16 bits of return status */
70 #define DITEM_LEAVE_MENU        (1 << 16)
71 #define DITEM_RECREATE          (1 << 18)
72
73 /* for use in describing more exotic behaviors */
74 typedef struct dialogMenuItem {
75         char *prompt;
76         char *title;
77         int (*fire)(struct dialogMenuItem *self);
78         void *data;
79 } dialogMenuItem;
80
81 static int
82 xdialog_count_rows(const char *p)
83 {
84         int rows = 0;
85
86         while ((p = strchr(p, '\n')) != NULL) {
87                 p++;
88                 if (*p == '\0')
89                         break;
90                 rows++;
91         }
92
93         return rows ? rows : 1;
94 }
95
96 static int
97 xdialog_count_columns(const char *p)
98 {
99         int len;
100         int max_len = 0;
101         const char *q;
102
103         for (; (q = strchr(p, '\n')) != NULL; p = q + 1) {
104                 len = q - p;
105                 max_len = MAX(max_len, len);
106         }
107
108         len = strlen(p);
109         max_len = MAX(max_len, len);
110         return max_len;
111 }
112
113 static int
114 xdialog_menu(const char *title, const char *cprompt, int height, int width,
115              int menu_height, int item_no, dialogMenuItem *ditems)
116 {
117         int i, result, choice = 0;
118         DIALOG_LISTITEM *listitems;
119         DIALOG_VARS save_vars;
120
121         dlg_save_vars(&save_vars);
122
123         /* initialize list items */
124         listitems = dlg_calloc(DIALOG_LISTITEM, item_no + 1);
125         assert_ptr(listitems, "xdialog_menu");
126         for (i = 0; i < item_no; i++) {
127                 listitems[i].name = ditems[i].prompt;
128                 listitems[i].text = ditems[i].title;
129         }
130
131         /* calculate height */
132         if (height < 0)
133                 height = xdialog_count_rows(cprompt) + menu_height + 4 + 2;
134         if (height > LINES)
135                 height = LINES;
136
137         /* calculate width */
138         if (width < 0) {
139                 int tag_x = 0;
140
141                 for (i = 0; i < item_no; i++) {
142                         int j, l;
143
144                         l = strlen(listitems[i].name);
145                         for (j = 0; j < item_no; j++) {
146                                 int k = strlen(listitems[j].text);
147                                 tag_x = MAX(tag_x, l + k + 2);
148                         }
149                 }
150                 width = MAX(xdialog_count_columns(cprompt), title != NULL ? xdialog_count_columns(title) : 0);
151                 width = MAX(width, tag_x + 4) + 4;
152         }
153         width = MAX(width, 24);
154         if (width > COLS)
155                 width = COLS;
156
157 again:
158         dialog_vars.default_item = listitems[choice].name;
159         result = dlg_menu(title, cprompt, height, width,
160             menu_height, item_no, listitems, &choice, NULL);
161         switch (result) {
162         case DLG_EXIT_ESC:
163                 result = -1;
164                 break;
165         case DLG_EXIT_OK:
166                 if (ditems[choice].fire != NULL) {
167                         int status;
168
169                         status = ditems[choice].fire(ditems + choice);
170                         if (status & DITEM_RECREATE) {
171                                 dlg_clear();
172                                 goto again;
173                         }
174                 }
175                 result = 0;
176                 break;
177         case DLG_EXIT_CANCEL:
178         default:
179                 result = 1;
180                 break;
181         }
182
183         free(listitems);
184         dlg_restore_vars(&save_vars);
185         return result;
186 }
187
188 static char     path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN],
189                 path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN],
190                 path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN];
191
192 static int reallydoit = 1;
193 static int reinstall = 0;
194 static int usedialog = 1;
195 static char *chrootenv = NULL;
196
197 static void     usage(void);
198 static int      confirm_zone(const char *filename);
199 static int      continent_country_menu(dialogMenuItem *);
200 static int      install_zoneinfo_file(const char *zoneinfo_file);
201 static int      set_zone_multi(dialogMenuItem *);
202 static int      set_zone_whole_country(dialogMenuItem *);
203 static int      set_zone_menu(dialogMenuItem *);
204 static int      set_zone_utc(void);
205
206 struct continent {
207         dialogMenuItem *menu;
208         int             nitems;
209 };
210
211 static struct continent africa, america, antarctica, arctic, asia, atlantic;
212 static struct continent australia, europe, indian, pacific, utc;
213
214 static struct continent_names {
215         const char      *name;
216         struct continent *continent;
217 } continent_names[] = {
218         { "Africa",     &africa },
219         { "America",    &america },
220         { "Antarctica", &antarctica },
221         { "Arctic",     &arctic },
222         { "Asia",       &asia },
223         { "Atlantic",   &atlantic },
224         { "Australia",  &australia },
225         { "Europe",     &europe },
226         { "Indian",     &indian },
227         { "Pacific",    &pacific },
228         { "UTC",        &utc }
229 };
230
231 static struct continent_items {
232         char            prompt[2];
233         char            title[30];
234 } continent_items[] = {
235         { "1",  "Africa" },
236         { "2",  "America -- North and South" },
237         { "3",  "Antarctica" },
238         { "4",  "Arctic Ocean" },
239         { "5",  "Asia" },
240         { "6",  "Atlantic Ocean" },
241         { "7",  "Australia" },
242         { "8",  "Europe" },
243         { "9",  "Indian Ocean" },
244         { "0",  "Pacific Ocean" },
245         { "a",  "UTC" }
246 };
247
248 #define NCONTINENTS     \
249     (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
250 static dialogMenuItem continents[NCONTINENTS];
251
252 #define OCEANP(x)       ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
253
254 static int
255 continent_country_menu(dialogMenuItem *continent)
256 {
257         char            title[64], prompt[64];
258         struct continent *contp = continent->data;
259         int             isocean = OCEANP(continent - continents);
260         int             menulen;
261         int             rv;
262
263         if (strcmp(continent->title, "UTC") == 0)
264                 return set_zone_utc();
265
266         /* Short cut -- if there's only one country, don't post a menu. */
267         if (contp->nitems == 1)
268                 return (contp->menu[0].fire(&contp->menu[0]));
269
270         /* It's amazing how much good grammar really matters... */
271         if (!isocean) {
272                 snprintf(title, sizeof(title), "Countries in %s",
273                     continent->title);
274                 snprintf(prompt, sizeof(prompt), "Select a country or region");
275         } else {
276                 snprintf(title, sizeof(title), "Islands and groups in the %s",
277                     continent->title);
278                 snprintf(prompt, sizeof(prompt), "Select an island or group");
279         }
280
281         menulen = contp->nitems < 16 ? contp->nitems : 16;
282         rv = xdialog_menu(title, prompt, -1, -1, menulen, contp->nitems,
283             contp->menu);
284         if (rv == 0)
285                 return (DITEM_LEAVE_MENU);
286         return (DITEM_RECREATE);
287 }
288
289 static struct continent *
290 find_continent(const char *name)
291 {
292         int             i;
293
294         for (i = 0; i < NCONTINENTS; i++)
295                 if (strcmp(name, continent_names[i].name) == 0)
296                         return (continent_names[i].continent);
297         return (0);
298 }
299
300 struct country {
301         char            *name;
302         char            *tlc;
303         int             nzones;
304         char            *filename;      /* use iff nzones < 0 */
305         struct continent *continent;    /* use iff nzones < 0 */
306         TAILQ_HEAD(, zone) zones;       /* use iff nzones > 0 */
307         dialogMenuItem  *submenu;       /* use iff nzones > 0 */
308 };
309
310 struct zone {
311         TAILQ_ENTRY(zone) link;
312         char            *descr;
313         char            *filename;
314         struct continent *continent;
315 };
316
317 /*
318  * This is the easiest organization... we use ISO 3166 country codes,
319  * of the two-letter variety, so we just size this array to suit.
320  * Beats worrying about dynamic allocation.
321  */
322 #define NCOUNTRIES      (26 * 26)
323 static struct country countries[NCOUNTRIES];
324
325 #define CODE2INT(s)     ((s[0] - 'A') * 26 + (s[1] - 'A'))
326
327 /*
328  * Read the ISO 3166 country code database in _PATH_ISO3166
329  * (/usr/share/misc/iso3166).  On error, exit via err(3).
330  */
331 static void
332 read_iso3166_table(void)
333 {
334         FILE            *fp;
335         struct country  *cp;
336         size_t          len;
337         char            *s, *t, *name;
338         int             lineno;
339
340         fp = fopen(path_iso3166, "r");
341         if (!fp)
342                 err(1, "%s", path_iso3166);
343         lineno = 0;
344
345         while ((s = fgetln(fp, &len)) != NULL) {
346                 lineno++;
347                 if (s[len - 1] != '\n')
348                         errx(1, "%s:%d: invalid format", path_iso3166, lineno);
349                 s[len - 1] = '\0';
350                 if (s[0] == '#' || strspn(s, " \t") == len - 1)
351                         continue;
352
353                 /* Isolate the two-letter code. */
354                 t = strsep(&s, "\t");
355                 if (t == NULL || strlen(t) != 2)
356                         errx(1, "%s:%d: invalid format", path_iso3166, lineno);
357                 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
358                         errx(1, "%s:%d: invalid code `%s'", path_iso3166,
359                             lineno, t);
360
361                 name = s;
362
363                 cp = &countries[CODE2INT(t)];
364                 if (cp->name)
365                         errx(1, "%s:%d: country code `%s' multiply defined: %s",
366                             path_iso3166, lineno, t, cp->name);
367                 cp->name = strdup(name);
368                 if (cp->name == NULL)
369                         errx(1, "malloc failed");
370                 cp->tlc = strdup(t);
371                 if (cp->tlc == NULL)
372                         errx(1, "malloc failed");
373         }
374
375         fclose(fp);
376 }
377
378 static void
379 add_zone_to_country(int lineno, const char *tlc, const char *descr,
380     const char *file, struct continent *cont)
381 {
382         struct zone     *zp;
383         struct country  *cp;
384
385         if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
386                 errx(1, "%s:%d: country code `%s' invalid", path_zonetab,
387                     lineno, tlc);
388
389         cp = &countries[CODE2INT(tlc)];
390         if (cp->name == 0)
391                 errx(1, "%s:%d: country code `%s' unknown", path_zonetab,
392                     lineno, tlc);
393
394         if (descr) {
395                 if (cp->nzones < 0)
396                         errx(1, "%s:%d: conflicting zone definition",
397                             path_zonetab, lineno);
398
399                 zp = malloc(sizeof(*zp));
400                 if (zp == NULL)
401                         errx(1, "malloc(%zu)", sizeof(*zp));
402
403                 if (cp->nzones == 0)
404                         TAILQ_INIT(&cp->zones);
405
406                 zp->descr = strdup(descr);
407                 if (zp->descr == NULL)
408                         errx(1, "malloc failed");
409                 zp->filename = strdup(file);
410                 if (zp->filename == NULL)
411                         errx(1, "malloc failed");
412                 zp->continent = cont;
413                 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
414                 cp->nzones++;
415         } else {
416                 if (cp->nzones > 0)
417                         errx(1, "%s:%d: zone must have description",
418                             path_zonetab, lineno);
419                 if (cp->nzones < 0)
420                         errx(1, "%s:%d: zone multiply defined",
421                             path_zonetab, lineno);
422                 cp->nzones = -1;
423                 cp->filename = strdup(file);
424                 if (cp->filename == NULL)
425                         errx(1, "malloc failed");
426                 cp->continent = cont;
427         }
428 }
429
430 /*
431  * This comparison function intentionally sorts all of the null-named
432  * ``countries''---i.e., the codes that don't correspond to a real
433  * country---to the end.  Everything else is lexical by country name.
434  */
435 static int
436 compare_countries(const void *xa, const void *xb)
437 {
438         const struct country *a = xa, *b = xb;
439
440         if (a->name == 0 && b->name == 0)
441                 return (0);
442         if (a->name == 0 && b->name != 0)
443                 return (1);
444         if (b->name == 0)
445                 return (-1);
446
447         return (strcmp(a->name, b->name));
448 }
449
450 /*
451  * This must be done AFTER all zone descriptions are read, since it breaks
452  * CODE2INT().
453  */
454 static void
455 sort_countries(void)
456 {
457
458         qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
459 }
460
461 static void
462 read_zones(void)
463 {
464         char            contbuf[16];
465         FILE            *fp;
466         struct continent *cont;
467         size_t          len;
468         char            *line, *tlc, *coord, *file, *descr, *p;
469         int             lineno;
470
471         fp = fopen(path_zonetab, "r");
472         if (!fp)
473                 err(1, "%s", path_zonetab);
474         lineno = 0;
475
476         while ((line = fgetln(fp, &len)) != NULL) {
477                 lineno++;
478                 if (line[len - 1] != '\n')
479                         errx(1, "%s:%d: invalid format", path_zonetab, lineno);
480                 line[len - 1] = '\0';
481                 if (line[0] == '#')
482                         continue;
483
484                 tlc = strsep(&line, "\t");
485                 if (strlen(tlc) != 2)
486                         errx(1, "%s:%d: invalid country code `%s'",
487                             path_zonetab, lineno, tlc);
488                 coord = strsep(&line, "\t");     /* Unused */
489                 file = strsep(&line, "\t");
490                 p = strchr(file, '/');
491                 if (p == NULL)
492                         errx(1, "%s:%d: invalid zone name `%s'", path_zonetab,
493                             lineno, file);
494                 contbuf[0] = '\0';
495                 strncat(contbuf, file, p - file);
496                 cont = find_continent(contbuf);
497                 if (!cont)
498                         errx(1, "%s:%d: invalid region `%s'", path_zonetab,
499                             lineno, contbuf);
500
501                 descr = (line != NULL && *line != '\0') ? line : NULL;
502
503                 add_zone_to_country(lineno, tlc, descr, file, cont);
504         }
505         fclose(fp);
506 }
507
508 static void
509 make_menus(void)
510 {
511         struct country  *cp;
512         struct zone     *zp, *zp2;
513         struct continent *cont;
514         dialogMenuItem  *dmi;
515         int             i;
516
517         /*
518          * First, count up all the countries in each continent/ocean.
519          * Be careful to count those countries which have multiple zones
520          * only once for each.  NB: some countries are in multiple
521          * continents/oceans.
522          */
523         for (cp = countries; cp->name; cp++) {
524                 if (cp->nzones == 0)
525                         continue;
526                 if (cp->nzones < 0) {
527                         cp->continent->nitems++;
528                 } else {
529                         TAILQ_FOREACH(zp, &cp->zones, link) {
530                                 cont = zp->continent;
531                                 for (zp2 = TAILQ_FIRST(&cp->zones);
532                                     zp2->continent != cont;
533                                     zp2 = TAILQ_NEXT(zp2, link))
534                                         ;
535                                 if (zp2 == zp)
536                                         zp->continent->nitems++;
537                         }
538                 }
539         }
540
541         /*
542          * Now allocate memory for the country menus and initialize
543          * continent menus.  We set nitems back to zero so that we can
544          * use it for counting again when we actually build the menus.
545          */
546         memset(continents, 0, sizeof(continents));
547         for (i = 0; i < NCONTINENTS; i++) {
548                 continent_names[i].continent->menu =
549                     malloc(sizeof(dialogMenuItem) *
550                     continent_names[i].continent->nitems);
551                 if (continent_names[i].continent->menu == NULL)
552                         errx(1, "malloc for continent menu");
553                 continent_names[i].continent->nitems = 0;
554                 continents[i].prompt = continent_items[i].prompt;
555                 continents[i].title = continent_items[i].title;
556                 continents[i].fire = continent_country_menu;
557                 continents[i].data = continent_names[i].continent;
558         }
559
560         /*
561          * Now that memory is allocated, create the menu items for
562          * each continent.  For multiple-zone countries, also create
563          * the country's zone submenu.
564          */
565         for (cp = countries; cp->name; cp++) {
566                 if (cp->nzones == 0)
567                         continue;
568                 if (cp->nzones < 0) {
569                         dmi = &cp->continent->menu[cp->continent->nitems];
570                         memset(dmi, 0, sizeof(*dmi));
571                         asprintf(&dmi->prompt, "%d", ++cp->continent->nitems);
572                         dmi->title = cp->name;
573                         dmi->fire = set_zone_whole_country;
574                         dmi->data = cp;
575                 } else {
576                         cp->submenu = malloc(cp->nzones * sizeof(*dmi));
577                         if (cp->submenu == 0)
578                                 errx(1, "malloc for submenu");
579                         cp->nzones = 0;
580                         TAILQ_FOREACH(zp, &cp->zones, link) {
581                                 cont = zp->continent;
582                                 dmi = &cp->submenu[cp->nzones];
583                                 memset(dmi, 0, sizeof(*dmi));
584                                 asprintf(&dmi->prompt, "%d", ++cp->nzones);
585                                 dmi->title = zp->descr;
586                                 dmi->fire = set_zone_multi;
587                                 dmi->data = zp;
588
589                                 for (zp2 = TAILQ_FIRST(&cp->zones);
590                                     zp2->continent != cont;
591                                     zp2 = TAILQ_NEXT(zp2, link))
592                                         ;
593                                 if (zp2 != zp)
594                                         continue;
595
596                                 dmi = &cont->menu[cont->nitems];
597                                 memset(dmi, 0, sizeof(*dmi));
598                                 asprintf(&dmi->prompt, "%d", ++cont->nitems);
599                                 dmi->title = cp->name;
600                                 dmi->fire = set_zone_menu;
601                                 dmi->data = cp;
602                         }
603                 }
604         }
605 }
606
607 static int
608 set_zone_menu(dialogMenuItem *dmi)
609 {
610         char            title[64], prompt[64];
611         struct country  *cp = dmi->data;
612         int             menulen;
613         int             rv;
614
615         snprintf(title, sizeof(title), "%s Time Zones", cp->name);
616         snprintf(prompt, sizeof(prompt),
617             "Select a zone which observes the same time as your locality.");
618         menulen = cp->nzones < 16 ? cp->nzones : 16;
619         rv = xdialog_menu(title, prompt, -1, -1, menulen, cp->nzones,
620             cp->submenu);
621         if (rv != 0)
622                 return (DITEM_RECREATE);
623         return (DITEM_LEAVE_MENU);
624 }
625
626 int
627 set_zone_utc(void)
628 {
629         if (!confirm_zone(NULL))
630                 return (DITEM_FAILURE | DITEM_RECREATE);
631
632         return (install_zoneinfo_file(NULL));
633 }
634
635 static int
636 install_zoneinfo_file(const char *zoneinfo_file)
637 {
638         char            buf[1024];
639         char            title[64], prompt[SILLY_BUFFER_SIZE];
640         struct stat     sb;
641         ssize_t         len;
642         int             fd1, fd2, copymode;
643
644         if (lstat(path_localtime, &sb) < 0) {
645                 /* Nothing there yet... */
646                 copymode = 1;
647         } else if (S_ISLNK(sb.st_mode))
648                 copymode = 0;
649         else
650                 copymode = 1;
651
652 #ifdef VERBOSE
653         snprintf(title, sizeof(title), "Info");
654         if (zoneinfo_file == NULL)
655                 snprintf(prompt, sizeof(prompt),
656                     "Removing %s", path_localtime);
657         else if (copymode)
658                 snprintf(prompt, sizeof(prompt),
659                     "Copying %s to %s", zoneinfo_file, path_localtime);
660         else
661                 snprintf(prompt, sizeof(prompt),
662                     "Creating symbolic link %s to %s",
663                     path_localtime, zoneinfo_file);
664         if (usedialog)
665                 dialog_msgbox(title, prompt, 8, 72, 1);
666         else
667                 fprintf(stderr, "%s\n", prompt);
668 #endif
669
670         if (reallydoit) {
671                 if (zoneinfo_file == NULL) {
672                         if (unlink(path_localtime) < 0 && errno != ENOENT) {
673                                 snprintf(title, sizeof(title), "Error");
674                                 snprintf(prompt, sizeof(prompt),
675                                      "Could not delete %s: %s", path_localtime,
676                                      strerror(errno));
677                                 if (usedialog)
678                                         dialog_msgbox(title, prompt, 8, 72, 1);
679                                 else
680                                         fprintf(stderr, "%s\n", prompt);
681
682                                 return (DITEM_FAILURE | DITEM_RECREATE);
683                         }
684                         if (unlink(path_db) < 0 && errno != ENOENT) {
685                                 snprintf(title, sizeof(title), "Error");
686                                 snprintf(prompt, sizeof(prompt),
687                                      "Could not delete %s: %s", path_db,
688                                      strerror(errno));
689                                 if (usedialog)
690                                         dialog_msgbox(title, prompt, 8, 72, 1);
691                                 else
692                                         fprintf(stderr, "%s\n", prompt);
693
694                                 return (DITEM_FAILURE | DITEM_RECREATE);
695                         }
696 #ifdef VERBOSE
697                         snprintf(title, sizeof(title), "Done");
698                         snprintf(prompt, sizeof(prompt),
699                             "Removed %s", path_localtime);
700                         if (usedialog)
701                                 dialog_msgbox(title, prompt, 8, 72, 1);
702                         else
703                                 fprintf(stderr, "%s\n", prompt);
704 #endif
705                         return (DITEM_LEAVE_MENU);
706                 }
707
708                 if (copymode) {
709                         fd1 = open(zoneinfo_file, O_RDONLY, 0);
710                         if (fd1 < 0) {
711                                 snprintf(title, sizeof(title), "Error");
712                                 snprintf(prompt, sizeof(prompt),
713                                     "Could not open %s: %s", zoneinfo_file,
714                                     strerror(errno));
715                                 if (usedialog)
716                                         dialog_msgbox(title, prompt, 8, 72, 1);
717                                 else
718                                         fprintf(stderr, "%s\n", prompt);
719                                 return (DITEM_FAILURE | DITEM_RECREATE);
720                         }
721
722                         if (unlink(path_localtime) < 0 && errno != ENOENT) {
723                                 snprintf(prompt, sizeof(prompt),
724                                     "Could not unlink %s: %s",
725                                     path_localtime, strerror(errno));
726                                 if (usedialog) {
727                                         snprintf(title, sizeof(title), "Error");
728                                         dialog_msgbox(title, prompt, 8, 72, 1);
729                                 } else
730                                         fprintf(stderr, "%s\n", prompt);
731                                 return (DITEM_FAILURE | DITEM_RECREATE);
732                         }
733
734                         fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY,
735                             S_IRUSR | S_IRGRP | S_IROTH);
736                         if (fd2 < 0) {
737                                 snprintf(title, sizeof(title), "Error");
738                                 snprintf(prompt, sizeof(prompt),
739                                     "Could not open %s: %s",
740                                     path_localtime, strerror(errno));
741                                 if (usedialog)
742                                         dialog_msgbox(title, prompt, 8, 72, 1);
743                                 else
744                                         fprintf(stderr, "%s\n", prompt);
745                                 return (DITEM_FAILURE | DITEM_RECREATE);
746                         }
747
748                         while ((len = read(fd1, buf, sizeof(buf))) > 0)
749                                 if ((len = write(fd2, buf, len)) < 0)
750                                         break;
751
752                         if (len == -1) {
753                                 snprintf(title, sizeof(title), "Error");
754                                 snprintf(prompt, sizeof(prompt),
755                                     "Error copying %s to %s %s", zoneinfo_file,
756                                     path_localtime, strerror(errno));
757                                 if (usedialog)
758                                         dialog_msgbox(title, prompt, 8, 72, 1);
759                                 else
760                                         fprintf(stderr, "%s\n", prompt);
761                                 /* Better to leave none than a corrupt one. */
762                                 unlink(path_localtime);
763                                 return (DITEM_FAILURE | DITEM_RECREATE);
764                         }
765                         close(fd1);
766                         close(fd2);
767                 } else {
768                         if (access(zoneinfo_file, R_OK) != 0) {
769                                 snprintf(title, sizeof(title), "Error");
770                                 snprintf(prompt, sizeof(prompt),
771                                     "Cannot access %s: %s", zoneinfo_file,
772                                     strerror(errno));
773                                 if (usedialog)
774                                         dialog_msgbox(title, prompt, 8, 72, 1);
775                                 else
776                                         fprintf(stderr, "%s\n", prompt);
777                                 return (DITEM_FAILURE | DITEM_RECREATE);
778                         }
779                         if (unlink(path_localtime) < 0 && errno != ENOENT) {
780                                 snprintf(prompt, sizeof(prompt),
781                                     "Could not unlink %s: %s",
782                                     path_localtime, strerror(errno));
783                                 if (usedialog) {
784                                         snprintf(title, sizeof(title), "Error");
785                                         dialog_msgbox(title, prompt, 8, 72, 1);
786                                 } else
787                                         fprintf(stderr, "%s\n", prompt);
788                                 return (DITEM_FAILURE | DITEM_RECREATE);
789                         }
790                         if (symlink(zoneinfo_file, path_localtime) < 0) {
791                                 snprintf(title, sizeof(title), "Error");
792                                 snprintf(prompt, sizeof(prompt),
793                                     "Cannot create symbolic link %s to %s: %s",
794                                     path_localtime, zoneinfo_file,
795                                     strerror(errno));
796                                 if (usedialog)
797                                         dialog_msgbox(title, prompt, 8, 72, 1);
798                                 else
799                                         fprintf(stderr, "%s\n", prompt);
800                                 return (DITEM_FAILURE | DITEM_RECREATE);
801                         }
802                 }
803
804 #ifdef VERBOSE
805                 snprintf(title, sizeof(title), "Done");
806                 if (copymode)
807                         snprintf(prompt, sizeof(prompt),
808                             "Copied timezone file from %s to %s",
809                             zoneinfo_file, path_localtime);
810                 else
811                         snprintf(prompt, sizeof(prompt),
812                             "Created symbolic link from %s to %s",
813                             zoneinfo_file, path_localtime);
814                 if (usedialog)
815                         dialog_msgbox(title, prompt, 8, 72, 1);
816                 else
817                         fprintf(stderr, "%s\n", prompt);
818 #endif
819         } /* reallydoit */
820
821         return (DITEM_LEAVE_MENU);
822 }
823
824 static int
825 install_zoneinfo(const char *zoneinfo)
826 {
827         int             rv;
828         FILE            *f;
829         char            path_zoneinfo_file[MAXPATHLEN];
830
831         sprintf(path_zoneinfo_file, "%s/%s", path_zoneinfo, zoneinfo);
832         rv = install_zoneinfo_file(path_zoneinfo_file);
833
834         /* Save knowledge for later */
835         if (reallydoit && (rv & DITEM_FAILURE) == 0) {
836                 if ((f = fopen(path_db, "w")) != NULL) {
837                         fprintf(f, "%s\n", zoneinfo);
838                         fclose(f);
839                 }
840         }
841
842         return (rv);
843 }
844
845 static int
846 confirm_zone(const char *filename)
847 {
848         char            title[64], prompt[64];
849         time_t          t = time(0);
850         struct tm       *tm;
851         int             rv;
852
853         setenv("TZ", filename == NULL ? "" : filename, 1);
854         tzset();
855         tm = localtime(&t);
856
857         snprintf(title, sizeof(title), "Confirmation");
858         snprintf(prompt, sizeof(prompt),
859             "Does the abbreviation `%s' look reasonable?", tm->tm_zone);
860         rv = !dialog_yesno(title, prompt, 5, 72);
861         return (rv);
862 }
863
864 static int
865 set_zone_multi(dialogMenuItem *dmi)
866 {
867         struct zone     *zp = dmi->data;
868         int             rv;
869
870         if (!confirm_zone(zp->filename))
871                 return (DITEM_FAILURE | DITEM_RECREATE);
872
873         rv = install_zoneinfo(zp->filename);
874         return (rv);
875 }
876
877 static int
878 set_zone_whole_country(dialogMenuItem *dmi)
879 {
880         struct country  *cp = dmi->data;
881         int             rv;
882
883         if (!confirm_zone(cp->filename))
884                 return (DITEM_FAILURE | DITEM_RECREATE);
885
886         rv = install_zoneinfo(cp->filename);
887         return (rv);
888 }
889
890 static void
891 usage(void)
892 {
893
894         fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]"
895             " [zoneinfo_file | zoneinfo_name]\n");
896         exit(1);
897 }
898
899 int
900 main(int argc, char **argv)
901 {
902         char            title[64], prompt[128];
903         int             c, fd, rv, skiputc;
904
905         skiputc = 0;
906         while ((c = getopt(argc, argv, "C:nrs")) != -1) {
907                 switch(c) {
908                 case 'C':
909                         chrootenv = optarg;
910                         break;
911                 case 'n':
912                         reallydoit = 0;
913                         break;
914                 case 'r':
915                         reinstall = 1;
916                         usedialog = 0;
917                         break;
918                 case 's':
919                         skiputc = 1;
920                         break;
921                 default:
922                         usage();
923                 }
924         }
925
926         if (argc - optind > 1)
927                 usage();
928
929         if (chrootenv == NULL) {
930                 strcpy(path_zonetab, _PATH_ZONETAB);
931                 strcpy(path_iso3166, _PATH_ISO3166);
932                 strcpy(path_zoneinfo, _PATH_ZONEINFO);
933                 strcpy(path_localtime, _PATH_LOCALTIME);
934                 strcpy(path_db, _PATH_DB);
935                 strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK);
936         } else {
937                 sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB);
938                 sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166);
939                 sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO);
940                 sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME);
941                 sprintf(path_db, "%s/%s", chrootenv, _PATH_DB);
942                 sprintf(path_wall_cmos_clock, "%s/%s", chrootenv,
943                     _PATH_WALL_CMOS_CLOCK);
944         }
945
946
947         /* Override the user-supplied umask. */
948         umask(S_IWGRP | S_IWOTH);
949
950         if (reinstall == 1) {
951                 FILE *f;
952                 char zoneinfo[MAXPATHLEN];
953
954                 if ((f = fopen(path_db, "r")) != NULL) {
955                         if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) {
956                                 zoneinfo[sizeof(zoneinfo) - 1] = 0;
957                                 if (strlen(zoneinfo) > 0) {
958                                         zoneinfo[strlen(zoneinfo) - 1] = 0;
959                                         rv = install_zoneinfo(zoneinfo);
960                                         exit(rv & ~DITEM_LEAVE_MENU);
961                                 }
962                                 errx(1, "Error reading %s.\n", path_db);
963                         }
964                         fclose(f);
965                         errx(1,
966                             "Unable to determine earlier installed zoneinfo "
967                             "name. Check %s", path_db);
968                 }
969                 errx(1, "Cannot open %s for reading. Does it exist?", path_db);
970         }
971
972         /*
973          * If the arguments on the command-line do not specify a file,
974          * then interpret it as a zoneinfo name
975          */
976         if (optind == argc - 1) {
977                 struct stat sb;
978
979                 if (stat(argv[optind], &sb) != 0) {
980                         usedialog = 0;
981                         rv = install_zoneinfo(argv[optind]);
982                         exit(rv & ~DITEM_LEAVE_MENU);
983                 }
984                 /* FALLTHROUGH */
985         }
986
987         read_iso3166_table();
988         read_zones();
989         sort_countries();
990         make_menus();
991
992         init_dialog(stdin, stdout);
993         if (skiputc == 0) {
994                 DIALOG_VARS save_vars;
995                 int yesno;
996
997                 snprintf(title, sizeof(title),
998                     "Select local or UTC (Greenwich Mean Time) clock");
999                 snprintf(prompt, sizeof(prompt),
1000                     "Is this machine's CMOS clock set to UTC?  "
1001                     "If it is set to local time,\n"
1002                     "or you don't know, please choose NO here!");
1003                 dlg_save_vars(&save_vars);
1004                 yesno = dialog_yesno(title, prompt, 7, 73);
1005                 dlg_restore_vars(&save_vars);
1006                 if (!yesno) {
1007                         if (reallydoit)
1008                                 unlink(path_wall_cmos_clock);
1009                 } else {
1010                         if (reallydoit) {
1011                                 fd = open(path_wall_cmos_clock,
1012                                     O_WRONLY | O_CREAT | O_TRUNC,
1013                                     S_IRUSR | S_IRGRP | S_IROTH);
1014                                 if (fd < 0) {
1015                                         end_dialog();
1016                                         err(1, "create %s",
1017                                             path_wall_cmos_clock);
1018                                 }
1019                                 close(fd);
1020                         }
1021                 }
1022                 dlg_clear();
1023         }
1024         if (optind == argc - 1) {
1025                 snprintf(title, sizeof(title), "Default timezone provided");
1026                 snprintf(prompt, sizeof(prompt),
1027                     "\nUse the default `%s' zone?", argv[optind]);
1028                 if (!dialog_yesno(title, prompt, 7, 72)) {
1029                         rv = install_zoneinfo_file(argv[optind]);
1030                         dlg_clear();
1031                         end_dialog();
1032                         exit(rv & ~DITEM_LEAVE_MENU);
1033                 }
1034                 dlg_clear();
1035         }
1036         snprintf(title, sizeof(title), "Time Zone Selector");
1037         snprintf(prompt, sizeof(prompt), "Select a region");
1038         xdialog_menu(title, prompt, -1, -1, NCONTINENTS, NCONTINENTS,
1039             continents);
1040
1041         dlg_clear();
1042         end_dialog();
1043         return (0);
1044 }