diff options
Diffstat (limited to 'usr.sbin/tzsetup/tzsetup.c')
-rw-r--r-- | usr.sbin/tzsetup/tzsetup.c | 1022 |
1 files changed, 1022 insertions, 0 deletions
diff --git a/usr.sbin/tzsetup/tzsetup.c b/usr.sbin/tzsetup/tzsetup.c new file mode 100644 index 000000000000..7dffb236af18 --- /dev/null +++ b/usr.sbin/tzsetup/tzsetup.c @@ -0,0 +1,1022 @@ +/* + * Copyright 1996 Massachusetts Institute of Technology + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that both the above copyright notice and this + * permission notice appear in all copies, that both the above + * copyright notice and this permission notice appear in all + * supporting documentation, and that the name of M.I.T. not be used + * in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. M.I.T. makes + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS + * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT + * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Second attempt at a `tzmenu' program, using the separate description + * files provided in newer tzdata releases. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <sys/fcntl.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/sysctl.h> + +#ifdef HAVE_BSDDIALOG +#include <bsddialog.h> +#include <locale.h> +#endif + +#define _PATH_ZONETAB "/usr/share/zoneinfo/zone1970.tab" +#define _PATH_ISO3166 "/usr/share/misc/iso3166" +#define _PATH_ZONEINFO "/usr/share/zoneinfo" +#define _PATH_LOCALTIME "/etc/localtime" +#define _PATH_DB "/var/db/zoneinfo" +#define _PATH_WALL_CMOS_CLOCK "/etc/wall_cmos_clock" + +#ifdef PATH_MAX +#define SILLY_BUFFER_SIZE 2*PATH_MAX +#else +#warning "Somebody needs to fix this to dynamically size this buffer." +#define SILLY_BUFFER_SIZE 2048 +#endif + +/* special return codes for `fire' actions */ +#define DITEM_FAILURE 1 + +/* flags - returned in upper 16 bits of return status */ +#define DITEM_LEAVE_MENU (1 << 16) +#define DITEM_RECREATE (1 << 18) + +static char path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN], + path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN], + path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN]; + +static int reallydoit = 1; +static int reinstall = 0; +static char *chrootenv = NULL; + +static void usage(void); +static int install_zoneinfo(const char *zoneinfo); +static int install_zoneinfo_file(const char *zoneinfo_file); + +#ifdef HAVE_BSDDIALOG +static struct bsddialog_conf conf; + +/* for use in describing more exotic behaviors */ +typedef struct dialogMenuItem { + char *prompt; + char *title; + int (*fire)(struct dialogMenuItem *self); + void *data; +} dialogMenuItem; + +static int +xdialog_menu(char *title, char *cprompt, int height, int width, + int menu_height, int item_no, dialogMenuItem *ditems) +{ + int i, result, choice = 0; + struct bsddialog_menuitem *listitems; + + /* initialize list items */ + listitems = calloc(item_no + 1, sizeof(struct bsddialog_menuitem)); + if (listitems == NULL) + errx(1, "Failed to allocate memory in xdialog_menu"); + for (i = 0; i < item_no; i++) { + listitems[i].prefix = ""; + listitems[i].depth = 0; + listitems[i].bottomdesc = ""; + listitems[i].on = false; + listitems[i].name = ditems[i].prompt; + listitems[i].desc = ditems[i].title; + } + + if (height < 0) + height = BSDDIALOG_AUTOSIZE; + + if (width < 0) { + width = BSDDIALOG_AUTOSIZE; + conf.auto_minwidth = 24; + } + +again: + conf.title = title; + result = bsddialog_menu(&conf, cprompt, height, width, + menu_height, item_no, listitems, &choice); + switch (result) { + case BSDDIALOG_ESC: + result = -1; + break; + case BSDDIALOG_OK: + if (ditems[choice].fire != NULL) { + int status; + + status = ditems[choice].fire(ditems + choice); + if (status & DITEM_RECREATE) { + goto again; + } + } + result = 0; + break; + case BSDDIALOG_CANCEL: + default: + result = 1; + break; + } + + free(listitems); + return (result); +} + +static int usedialog = 1; + +static int confirm_zone(const char *filename); +static int continent_country_menu(dialogMenuItem *); +static int set_zone_multi(dialogMenuItem *); +static int set_zone_whole_country(dialogMenuItem *); +static int set_zone_menu(dialogMenuItem *); +static int set_zone_utc(void); + +struct continent { + dialogMenuItem *menu; + int nitems; +}; + +static struct continent africa, america, antarctica, asia, atlantic; +static struct continent australia, europe, indian, pacific, utc; + +static struct continent_names { + const char *name; + struct continent *continent; +} continent_names[] = { + { "Africa", &africa }, + { "America", &america }, + { "Antarctica", &antarctica }, + { "Asia", &asia }, + { "Atlantic", &atlantic }, + { "Australia", &australia }, + { "Europe", &europe }, + { "Indian", &indian }, + { "Pacific", &pacific }, + { "UTC", &utc } +}; + +static struct continent_items { + char prompt[2]; + char title[30]; +} continent_items[] = { + { "1", "Africa" }, + { "2", "America -- North and South" }, + { "3", "Antarctica" }, + { "4", "Asia" }, + { "5", "Atlantic Ocean" }, + { "6", "Australia" }, + { "7", "Europe" }, + { "8", "Indian Ocean" }, + { "9", "Pacific Ocean" }, + { "0", "UTC" } +}; + +#define NCONTINENTS \ + (int)((sizeof(continent_items)) / (sizeof(continent_items[0]))) +static dialogMenuItem continents[NCONTINENTS]; + +#define OCEANP(x) ((x) == 4 || (x) == 7 || (x) == 8) + +static int +continent_country_menu(dialogMenuItem *continent) +{ + char title[64], prompt[64]; + struct continent *contp = continent->data; + int isocean = OCEANP(continent - continents); + int menulen; + int rv; + + if (strcmp(continent->title, "UTC") == 0) + return (set_zone_utc()); + + /* Short cut -- if there's only one country, don't post a menu. */ + if (contp->nitems == 1) + return (contp->menu[0].fire(&contp->menu[0])); + + /* It's amazing how much good grammar really matters... */ + if (!isocean) { + snprintf(title, sizeof(title), "Countries in %s", + continent->title); + snprintf(prompt, sizeof(prompt), "Select a country or region"); + } else { + snprintf(title, sizeof(title), "Islands and groups in the %s", + continent->title); + snprintf(prompt, sizeof(prompt), "Select an island or group"); + } + + menulen = contp->nitems < 16 ? contp->nitems : 16; + rv = xdialog_menu(title, prompt, -1, -1, menulen, contp->nitems, + contp->menu); + if (rv == 0) + return (DITEM_LEAVE_MENU); + return (DITEM_RECREATE); +} + +static struct continent * +find_continent(const char *name) +{ + int i; + + for (i = 0; i < NCONTINENTS; i++) + if (strcmp(name, continent_names[i].name) == 0) + return (continent_names[i].continent); + return (0); +} + +struct country { + char *name; + char *tlc; + int nzones; + char *filename; /* use iff nzones < 0 */ + struct continent *continent; /* use iff nzones < 0 */ + TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */ + dialogMenuItem *submenu; /* use iff nzones > 0 */ +}; + +struct zone { + TAILQ_ENTRY(zone) link; + char *descr; + char *filename; + struct continent *continent; +}; + +/* + * This is the easiest organization... we use ISO 3166 country codes, + * of the two-letter variety, so we just size this array to suit. + * Beats worrying about dynamic allocation. + */ +#define NCOUNTRIES (26 * 26) +static struct country countries[NCOUNTRIES]; + +#define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A')) + +/* + * Read the ISO 3166 country code database in _PATH_ISO3166 + * (/usr/share/misc/iso3166). On error, exit via err(3). + */ +static void +read_iso3166_table(void) +{ + FILE *fp; + struct country *cp; + size_t len; + char *s, *t, *name; + int lineno; + + fp = fopen(path_iso3166, "r"); + if (!fp) + err(1, "%s", path_iso3166); + lineno = 0; + + while ((s = fgetln(fp, &len)) != NULL) { + lineno++; + if (s[len - 1] != '\n') + errx(1, "%s:%d: invalid format", path_iso3166, lineno); + s[len - 1] = '\0'; + if (s[0] == '#' || strspn(s, " \t") == len - 1) + continue; + + /* Isolate the two-letter code. */ + t = strsep(&s, "\t"); + if (t == NULL || strlen(t) != 2) + errx(1, "%s:%d: invalid format", path_iso3166, lineno); + if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z') + errx(1, "%s:%d: invalid code `%s'", path_iso3166, + lineno, t); + + /* Now skip past the three-letter and numeric codes. */ + name = strsep(&s, "\t"); /* 3-let */ + if (name == NULL || strlen(name) != 3) + errx(1, "%s:%d: invalid format", path_iso3166, lineno); + name = strsep(&s, "\t"); /* numeric */ + if (name == NULL || strlen(name) != 3) + errx(1, "%s:%d: invalid format", path_iso3166, lineno); + + name = s; + + cp = &countries[CODE2INT(t)]; + if (cp->name) + errx(1, "%s:%d: country code `%s' multiply defined: %s", + path_iso3166, lineno, t, cp->name); + cp->name = strdup(name); + if (cp->name == NULL) + errx(1, "malloc failed"); + cp->tlc = strdup(t); + if (cp->tlc == NULL) + errx(1, "malloc failed"); + } + + fclose(fp); +} + +static void +add_zone_to_country(int lineno, const char *tlc, const char *descr, + const char *file, struct continent *cont) +{ + struct zone *zp; + struct country *cp; + + if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') + errx(1, "%s:%d: country code `%s' invalid", path_zonetab, + lineno, tlc); + + cp = &countries[CODE2INT(tlc)]; + if (cp->name == 0) + errx(1, "%s:%d: country code `%s' unknown", path_zonetab, + lineno, tlc); + + if (descr) { + if (cp->nzones < 0) + errx(1, "%s:%d: conflicting zone definition", + path_zonetab, lineno); + + zp = malloc(sizeof(*zp)); + if (zp == NULL) + errx(1, "malloc(%zu)", sizeof(*zp)); + + if (cp->nzones == 0) + TAILQ_INIT(&cp->zones); + + zp->descr = strdup(descr); + if (zp->descr == NULL) + errx(1, "malloc failed"); + zp->filename = strdup(file); + if (zp->filename == NULL) + errx(1, "malloc failed"); + zp->continent = cont; + TAILQ_INSERT_TAIL(&cp->zones, zp, link); + cp->nzones++; + } else { + if (cp->nzones > 0) + errx(1, "%s:%d: zone must have description", + path_zonetab, lineno); + if (cp->nzones < 0) + errx(1, "%s:%d: zone multiply defined", + path_zonetab, lineno); + cp->nzones = -1; + cp->filename = strdup(file); + if (cp->filename == NULL) + errx(1, "malloc failed"); + cp->continent = cont; + } +} + +/* + * This comparison function intentionally sorts all of the null-named + * ``countries''---i.e., the codes that don't correspond to a real + * country---to the end. Everything else is lexical by country name. + */ +static int +compare_countries(const void *xa, const void *xb) +{ + const struct country *a = xa, *b = xb; + + if (a->name == 0 && b->name == 0) + return (0); + if (a->name == 0 && b->name != 0) + return (1); + if (b->name == 0) + return (-1); + + return (strcmp(a->name, b->name)); +} + +/* + * This must be done AFTER all zone descriptions are read, since it breaks + * CODE2INT(). + */ +static void +sort_countries(void) +{ + + qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries); +} + +static void +read_zones(void) +{ + char contbuf[16]; + FILE *fp; + struct continent *cont; + size_t len, contlen; + char *line, *country_list, *tlc, *file, *descr, *p; + int lineno; + + fp = fopen(path_zonetab, "r"); + if (!fp) + err(1, "%s", path_zonetab); + lineno = 0; + + while ((line = fgetln(fp, &len)) != NULL) { + lineno++; + if (line[len - 1] != '\n') + errx(1, "%s:%d: invalid format", path_zonetab, lineno); + line[len - 1] = '\0'; + if (line[0] == '#') + continue; + + country_list = strsep(&line, "\t"); + /* coord = */ strsep(&line, "\t"); /* Unused */ + file = strsep(&line, "\t"); + /* get continent portion from continent/country */ + p = strchr(file, '/'); + if (p == NULL) + errx(1, "%s:%d: invalid zone name `%s'", path_zonetab, + lineno, file); + contlen = p - file + 1; /* trailing nul */ + if (contlen > sizeof(contbuf)) + errx(1, "%s:%d: continent name in zone name `%s' too long", + path_zonetab, lineno, file); + strlcpy(contbuf, file, contlen); + cont = find_continent(contbuf); + if (!cont) + errx(1, "%s:%d: invalid region `%s'", path_zonetab, + lineno, contbuf); + + descr = (line != NULL && *line != '\0') ? line : NULL; + + while (country_list != NULL) { + tlc = strsep(&country_list, ","); + if (strlen(tlc) != 2) + errx(1, "%s:%d: invalid country code `%s'", + path_zonetab, lineno, tlc); + add_zone_to_country(lineno, tlc, descr, file, cont); + } + } + fclose(fp); +} + +static void +make_menus(void) +{ + struct country *cp; + struct zone *zp, *zp2; + struct continent *cont; + dialogMenuItem *dmi; + int i; + + /* + * First, count up all the countries in each continent/ocean. + * Be careful to count those countries which have multiple zones + * only once for each. NB: some countries are in multiple + * continents/oceans. + */ + for (cp = countries; cp->name; cp++) { + if (cp->nzones == 0) + continue; + if (cp->nzones < 0) { + cp->continent->nitems++; + } else { + TAILQ_FOREACH(zp, &cp->zones, link) { + cont = zp->continent; + for (zp2 = TAILQ_FIRST(&cp->zones); + zp2->continent != cont; + zp2 = TAILQ_NEXT(zp2, link)) + ; + if (zp2 == zp) + zp->continent->nitems++; + } + } + } + + /* + * Now allocate memory for the country menus and initialize + * continent menus. We set nitems back to zero so that we can + * use it for counting again when we actually build the menus. + */ + memset(continents, 0, sizeof(continents)); + for (i = 0; i < NCONTINENTS; i++) { + continent_names[i].continent->menu = + malloc(sizeof(dialogMenuItem) * + continent_names[i].continent->nitems); + if (continent_names[i].continent->menu == NULL) + errx(1, "malloc for continent menu"); + continent_names[i].continent->nitems = 0; + continents[i].prompt = continent_items[i].prompt; + continents[i].title = continent_items[i].title; + continents[i].fire = continent_country_menu; + continents[i].data = continent_names[i].continent; + } + + /* + * Now that memory is allocated, create the menu items for + * each continent. For multiple-zone countries, also create + * the country's zone submenu. + */ + for (cp = countries; cp->name; cp++) { + if (cp->nzones == 0) + continue; + if (cp->nzones < 0) { + dmi = &cp->continent->menu[cp->continent->nitems]; + memset(dmi, 0, sizeof(*dmi)); + asprintf(&dmi->prompt, "%d", ++cp->continent->nitems); + dmi->title = cp->name; + dmi->fire = set_zone_whole_country; + dmi->data = cp; + } else { + cp->submenu = malloc(cp->nzones * sizeof(*dmi)); + if (cp->submenu == 0) + errx(1, "malloc for submenu"); + cp->nzones = 0; + TAILQ_FOREACH(zp, &cp->zones, link) { + cont = zp->continent; + dmi = &cp->submenu[cp->nzones]; + memset(dmi, 0, sizeof(*dmi)); + asprintf(&dmi->prompt, "%d", ++cp->nzones); + dmi->title = zp->descr; + dmi->fire = set_zone_multi; + dmi->data = zp; + + for (zp2 = TAILQ_FIRST(&cp->zones); + zp2->continent != cont; + zp2 = TAILQ_NEXT(zp2, link)) + ; + if (zp2 != zp) + continue; + + dmi = &cont->menu[cont->nitems]; + memset(dmi, 0, sizeof(*dmi)); + asprintf(&dmi->prompt, "%d", ++cont->nitems); + dmi->title = cp->name; + dmi->fire = set_zone_menu; + dmi->data = cp; + } + } + } +} + +static int +set_zone_menu(dialogMenuItem *dmi) +{ + char title[64], prompt[64]; + struct country *cp = dmi->data; + int menulen; + int rv; + + snprintf(title, sizeof(title), "%s Time Zones", cp->name); + snprintf(prompt, sizeof(prompt), + "Select a zone which observes the same time as your locality."); + menulen = cp->nzones < 16 ? cp->nzones : 16; + rv = xdialog_menu(title, prompt, -1, -1, menulen, cp->nzones, + cp->submenu); + if (rv != 0) + return (DITEM_RECREATE); + return (DITEM_LEAVE_MENU); +} + +static int +set_zone_utc(void) +{ + if (!confirm_zone("UTC")) + return (DITEM_FAILURE | DITEM_RECREATE); + + return (install_zoneinfo("UTC")); +} + +static int +confirm_zone(const char *filename) +{ + char prompt[64]; + time_t t = time(0); + struct tm *tm; + int rv; + + setenv("TZ", filename, 1); + tzset(); + tm = localtime(&t); + + snprintf(prompt, sizeof(prompt), + "Does the abbreviation `%s' look reasonable?", tm->tm_zone); + conf.title = "Confirmation"; + rv = !bsddialog_yesno(&conf, prompt, 5, 72); + return (rv); +} + +static int +set_zone_multi(dialogMenuItem *dmi) +{ + struct zone *zp = dmi->data; + int rv; + + if (!confirm_zone(zp->filename)) + return (DITEM_FAILURE | DITEM_RECREATE); + + rv = install_zoneinfo(zp->filename); + return (rv); +} + +static int +set_zone_whole_country(dialogMenuItem *dmi) +{ + struct country *cp = dmi->data; + int rv; + + if (!confirm_zone(cp->filename)) + return (DITEM_FAILURE | DITEM_RECREATE); + + rv = install_zoneinfo(cp->filename); + return (rv); +} + +#endif + +static int +install_zoneinfo_file(const char *zoneinfo_file) +{ + char buf[1024]; + char prompt[SILLY_BUFFER_SIZE]; + struct stat sb; + ssize_t len; + int fd1, fd2, copymode; + + if (lstat(path_localtime, &sb) < 0) { + /* Nothing there yet... */ + copymode = 1; + } else if (S_ISLNK(sb.st_mode)) + copymode = 0; + else + copymode = 1; + +#ifdef VERBOSE + if (copymode) + snprintf(prompt, sizeof(prompt), + "Copying %s to %s", zoneinfo_file, path_localtime); + else + snprintf(prompt, sizeof(prompt), + "Creating symbolic link %s to %s", + path_localtime, zoneinfo_file); +#ifdef HAVE_BSDDIALOG + if (usedialog) + conf.title = "Info"; + bsddialog_msgbox(&conf, prompt, 8, 72); + else +#endif + fprintf(stderr, "%s\n", prompt); +#endif + + if (reallydoit) { + if (copymode) { + fd1 = open(zoneinfo_file, O_RDONLY, 0); + if (fd1 < 0) { + snprintf(prompt, sizeof(prompt), + "Could not open %s: %s", zoneinfo_file, + strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + + if (unlink(path_localtime) < 0 && errno != ENOENT) { + snprintf(prompt, sizeof(prompt), + "Could not delete %s: %s", + path_localtime, strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + + fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IRGRP | S_IROTH); + if (fd2 < 0) { + snprintf(prompt, sizeof(prompt), + "Could not open %s: %s", + path_localtime, strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + + while ((len = read(fd1, buf, sizeof(buf))) > 0) + if ((len = write(fd2, buf, len)) < 0) + break; + + if (len == -1) { + snprintf(prompt, sizeof(prompt), + "Error copying %s to %s %s", zoneinfo_file, + path_localtime, strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + /* Better to leave none than a corrupt one. */ + unlink(path_localtime); + return (DITEM_FAILURE | DITEM_RECREATE); + } + close(fd1); + close(fd2); + } else { + if (access(zoneinfo_file, R_OK) != 0) { + snprintf(prompt, sizeof(prompt), + "Cannot access %s: %s", zoneinfo_file, + strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + if (unlink(path_localtime) < 0 && errno != ENOENT) { + snprintf(prompt, sizeof(prompt), + "Could not delete %s: %s", + path_localtime, strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + if (symlink(zoneinfo_file, path_localtime) < 0) { + snprintf(prompt, sizeof(prompt), + "Cannot create symbolic link %s to %s: %s", + path_localtime, zoneinfo_file, + strerror(errno)); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Error"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); + return (DITEM_FAILURE | DITEM_RECREATE); + } + } + +#ifdef VERBOSE + if (copymode) + snprintf(prompt, sizeof(prompt), + "Copied timezone file from %s to %s", + zoneinfo_file, path_localtime); + else + snprintf(prompt, sizeof(prompt), + "Created symbolic link from %s to %s", + zoneinfo_file, path_localtime); +#ifdef HAVE_BSDDIALOG + if (usedialog) { + conf.title = "Done"; + bsddialog_msgbox(&conf, prompt, 8, 72); + } else +#endif + fprintf(stderr, "%s\n", prompt); +#endif + } /* reallydoit */ + + return (DITEM_LEAVE_MENU); +} + +static int +install_zoneinfo(const char *zoneinfo) +{ + int rv; + FILE *f; + char path_zoneinfo_file[MAXPATHLEN]; + + if ((size_t)snprintf(path_zoneinfo_file, sizeof(path_zoneinfo_file), + "%s/%s", path_zoneinfo, zoneinfo) >= sizeof(path_zoneinfo_file)) + errx(1, "%s/%s name too long", path_zoneinfo, zoneinfo); + rv = install_zoneinfo_file(path_zoneinfo_file); + + /* Save knowledge for later */ + if (reallydoit && (rv & DITEM_FAILURE) == 0) { + if ((f = fopen(path_db, "w")) != NULL) { + fprintf(f, "%s\n", zoneinfo); + fclose(f); + } + } + + return (rv); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]" + " [zoneinfo_file | zoneinfo_name]\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ +#ifdef HAVE_BSDDIALOG + char prompt[128]; + int fd; +#endif + int c, rv, skiputc; + char vm_guest[16] = ""; + size_t len = sizeof(vm_guest); + + skiputc = 0; + +#ifdef HAVE_BSDDIALOG + setlocale(LC_ALL, ""); +#endif + + /* Default skiputc to 1 for VM guests */ + if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 && + strcmp(vm_guest, "none") != 0) + skiputc = 1; + + while ((c = getopt(argc, argv, "C:nrs")) != -1) { + switch(c) { + case 'C': + chrootenv = optarg; + break; + case 'n': + reallydoit = 0; + break; + case 'r': + reinstall = 1; +#ifdef HAVE_BSDDIALOG + usedialog = 0; +#endif + break; + case 's': + skiputc = 1; + break; + default: + usage(); + } + } + + if (argc - optind > 1) + usage(); + + if (chrootenv == NULL) { + strcpy(path_zonetab, _PATH_ZONETAB); + strcpy(path_iso3166, _PATH_ISO3166); + strcpy(path_zoneinfo, _PATH_ZONEINFO); + strcpy(path_localtime, _PATH_LOCALTIME); + strcpy(path_db, _PATH_DB); + strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK); + } else { + sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB); + sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166); + sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO); + sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME); + sprintf(path_db, "%s/%s", chrootenv, _PATH_DB); + sprintf(path_wall_cmos_clock, "%s/%s", chrootenv, + _PATH_WALL_CMOS_CLOCK); + } + + /* Override the user-supplied umask. */ + (void)umask(S_IWGRP | S_IWOTH); + + if (reinstall == 1) { + FILE *f; + char zoneinfo[MAXPATHLEN]; + + if ((f = fopen(path_db, "r")) != NULL) { + if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) { + zoneinfo[sizeof(zoneinfo) - 1] = 0; + if (strlen(zoneinfo) > 0) { + zoneinfo[strlen(zoneinfo) - 1] = 0; + rv = install_zoneinfo(zoneinfo); + exit(rv & ~DITEM_LEAVE_MENU); + } + errx(1, "Error reading %s.\n", path_db); + } + fclose(f); + errx(1, + "Unable to determine earlier installed zoneinfo " + "name. Check %s", path_db); + } + errx(1, "Cannot open %s for reading. Does it exist?", path_db); + } + + /* + * If the arguments on the command-line do not specify a file, + * then interpret it as a zoneinfo name + */ + if (optind == argc - 1) { + struct stat sb; + + if (stat(argv[optind], &sb) != 0) { +#ifdef HAVE_BSDDIALOG + usedialog = 0; +#endif + rv = install_zoneinfo(argv[optind]); + exit(rv & ~DITEM_LEAVE_MENU); + } + /* FALLTHROUGH */ + } +#ifdef HAVE_BSDDIALOG + + read_iso3166_table(); + read_zones(); + sort_countries(); + make_menus(); + + bsddialog_initconf(&conf); + conf.clear = true; + conf.key.enable_esc = true; + + if (bsddialog_init() == BSDDIALOG_ERROR) { + printf("Error bsddialog: %s\n", bsddialog_geterror()); + return (1); + } + + if (skiputc == 0) { + int yesno; + + snprintf(prompt, sizeof(prompt), + "Is this machine's CMOS clock set to UTC? " + "If it is set to local time,\n" + "or you don't know, please choose NO here!"); + + conf.button.default_cancel = false; + conf.title = "Select local or UTC (Greenwich Mean Time) clock"; + yesno = bsddialog_yesno(&conf, prompt, 7, 73); + if (!yesno) { + if (reallydoit) + unlink(path_wall_cmos_clock); + } else { + if (reallydoit) { + fd = open(path_wall_cmos_clock, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + bsddialog_end(); + err(1, "create %s", + path_wall_cmos_clock); + } + close(fd); + } + } + } + if (optind == argc - 1) { + snprintf(prompt, sizeof(prompt), + "\nUse the default `%s' zone?", argv[optind]); + conf.title = "Default timezone provided"; + if (!bsddialog_yesno(&conf, prompt, 7, 72)) { + rv = install_zoneinfo_file(argv[optind]); + bsddialog_end(); + exit(rv & ~DITEM_LEAVE_MENU); + } + } + xdialog_menu("Time Zone Selector", "Select a region", -1, -1, + NCONTINENTS, NCONTINENTS, continents); + + bsddialog_end(); +#else + usage(); +#endif + return (0); +} |