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