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