#!/bin/sh # -*- tab-width: 4 -*- ;; Emacs # vi: set tabstop=4 :: Vi/ViM # # Revision: 1.0.2 # Created: June 27th, 2011 # Last Modified: July 5th, 2011 ############################################################ COPYRIGHT # # Devin Teske (c)2011. All Rights Reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INLUDING, 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. # ############################################################ INFORMATION # # Description: # # Translation of FreeBSD's tzsetup(8) from C program based on dialog(3) to # sh(1) shell script based on dialog(1). Goals/reasons of this project are # as follows: # # 1. Act as a drop-in replacement for FreeBSD's tzsetup(8). # 2. Provide a Graphical User Interface (GUI) via Xdialog(1) -- an X11- # enabled drop-in replacement for dialog(1). # 3. To bring tzsetup(8) to Linux (replacing tzselect(8)) and other systems # that support dialog(1) and/or Xdialog(1). # 4. Provide a single interface for both Linux and FreeBSD users without # needing to ship two separate binaries. # # Command Usage: # # tzdialog [-nsvX] [default] # # OPTIONS: # -n Do not create or copy files. # -s Skip the initial question about adjusting the clock if # not set to UTC. # -v Verbose. Enable extra output when installing the zone file. # -X Enable the use of Xdialog(1) instead of dialog(1). # # Dependencies (sorted alphabetically): # # Xdialog(1)* awk(1) cat(1) dialog(1) grep(1) id(1) # printf(1)** rm(1) sort(1) strings(1) stty(1) uname(1) # which(1) # # * Optional # ** Is a shell-builtin on some releases # ############################################################ CONFIGURATION # # Default directory to store dialog(1) temporary files # : ${DIALOG_TMPDIR:="/tmp"} # # OS Glue # UNAME_S=$( uname -s ) # # Standard pathnames # _PATH_ZONETAB="/usr/share/zoneinfo/zone.tab" _PATH_ZONEINFO="/usr/share/zoneinfo" _PATH_LOCALTIME="/etc/localtime" case "$UNAME_S" in Linux|Darwin|CYGWIN_NT-*) _PATH_ISO3166="/usr/share/zoneinfo/iso3166.tab" ;; FreeBSD) _PATH_ISO3166="/usr/share/misc/iso3166" _PATH_WALL_CMOS_CLOCK="/etc/wall_cmos_clock" ;; *) for _PATH_ISO3166 in \ /usr/share/zoneinfo/iso3166.tab \ /usr/share/misc/iso3166 \ ; do [ -f "$_PATH_ISO3166" ] && break done [ -f "$_PATH_ISO3166" ] || die \ "%s: %s: No such file or directory" \ "$progname" "$_PATH_ISO3166" esac # # List of worldly continents/oceans # export CONTINENTS=" africa america antarctica arctic asia atlantic australia europe indian pacific " # # Directory name of each continent/ocean (in _PATH_ZONEINFO) # export continent_africa_name="Africa" export continent_america_name="America" export continent_antarctica_name="Antarctica" export continent_arctic_name="Arctic" export continent_asia_name="Asia" export continent_atlantic_name="Atlantic" export continent_australia_name="Australia" export continent_europe_name="Europe" export continent_indian_name="Indian" export continent_pacific_name="Pacific" # # Menu text of each continent/ocean # export continent_africa_title="Africa" export continent_america_title="America -- North and South" export continent_antarctica_title="Antarctica" export continent_arctic_title="Arctic Ocean" export continent_asia_title="Asia" export continent_atlantic_title="Atlantic Ocean" export continent_australia_title="Australia" export continent_europe_title="Europe" export continent_indian_title="Indian Ocean" export continent_pacific_title="Pacific Ocean" ############################################################ GLOBALS # # Global exit status variables # SUCCESS=0 FAILURE=1 # # Program name # progname="${0##*/}" # # Default name of dialog(1) utility # DIALOG="dialog" # # Settings used while interacting with dialog(1) # export DIALOG_MENU_TAGS="1234567890abcdefghijklmnopqrstuvwxyz" # # Not all implementations of dialog(1) support the `--defaultno' option. # NOTE: Initially assumed to be unsupported but tested later in MAIN # NOTE: Disable the test by setting CHECK_DIALOG_FOR_DEFAULTNO to NULL # NOTE: Passing `-X' will implicitly disable both of the following options # DIALOG_ENABLE_DEFAULTNO= CHECK_DIALOG_FOR_DEFAULTNO=1 # # Declare that we are fully-compliant with Xdialog(1) by unset'ing all # compatibility settings. # unset XDIALOG_HIGH_DIALOG_COMPAT unset XDIALOG_FORCE_AUTOSIZE unset XDIALOG_INFOBOX_TIMEOUT # # Xdialog(1) size considerations # Uncomment targeted minimum resolution # XDIALOG_MAX_SIZE="31 105" # for 640x480 #XDIALOG_MAX_SIZE="40 132" # for 800x600 #XDIALOG_MAX_SIZE="52 165" # for 1024x768 # # Options # REALLYDOIT=1 SKIPUTC= USE_XDIALOG= VERBOSE= # # Dummy vars (populated dynamically) # COUNTRIES= # list of 2-character country codes created by read_iso3166_table ############################################################################### ################################## FUNCTIONS ################################## ############################################################################### # have $anything # # A wrapper to the `type' built-in. Returns true if argument is a valid shell # built-in, keyword, or externally-tracked binary, otherwise false. # have() { type "$@" > /dev/null 2>&1 } # quietly $command [ $arguments ... ] # # Run a command quietly (quell any output to stdout or stderr). # quietly() { "$@" > /dev/null 2>&1 } # fprintf $fd $fmt [ $opts ... ] # # Like printf, except allows you to print to a specific file-descriptor. Useful # for printing to stderr (fd=2) or some other known file-descriptor. # fprintf() { local fd=$1 [ $# -gt 1 ] || return $FAILURE shift 1 printf "$@" >&$fd } # eprintf $fmt [ $opts ... ] # # Print a message to stderr (fd=2). # eprintf() { fprintf 2 "$@" } # die [ $fmt [ $opts ... ]] # # Optionally print a message to stderr before exiting with failure status. # die() { local fmt="$1" [ $# -gt 0 ] && shift 1 [ "$fmt" ] && eprintf "$fmt\n" "$@" exit $FAILURE } # usage # # Prints a short syntax statement and exits. # usage() { local optfmt="\t%-11s%s\n" eprintf "Usage: %s [-nsvX] [default]\n" "$progname" eprintf "OPTIONS:\n" eprintf "$optfmt" "-n" \ "Do not create or copy files." eprintf "$optfmt" "-s" \ "Skip the initial question about adjusting the clock if" eprintf "$optfmt" "" \ "not set to UTC." eprintf "$optfmt" "-v" \ "Verbose. Enable extra output when installing the zone file." eprintf "$optfmt" "-X" \ "Enable the use of Xdialog(1) instead of dialog(1)." die } # longest_line_length # # Simple wrapper to an awk(1) script to print the length of the longest line of # input (read from stdin). # longest_line_length_awk=' BEGIN { longest = 0 } { len = length($0) longest = ( len > longest ? len : longest ) } END { print longest } ' longest_line_length() { awk "$longest_line_length_awk" } ############################################################################### ############################# TIME-ZONE FUNCTIONS ############################# ############################################################################### # read_iso3166_table # # Read the ISO 3166 country code database in _PATH_ISO3166: # /usr/share/misc/iso3166 on FreeBSD # /usr/share/zoneinfo/iso3166.tab on Linux, Mac OS X, and Cygwin # # The format of this file on FreeBSD is: # two three number name # # The format of this file on Linux, Mac OS X, and Cygwin is: # two name # # With each of the following elements (described below) being separated by a # single tab character: # # two ISO 3166 2-character country code # three ISO 3166 3-character country code (if provided) # number ISO 3166 numeric country code (if provided) # name Human-readable country name (may contain spaces) # # Variables created by this function: # # COUNTRIES # A space-separated list of 2-character country codes. # country_*_name # The country `name' (as described above). # # where CODE is the 2-character country code. # # This function is a two-parter. Below is the awk(1) portion of the function, # afterward is the sh(1) function which utilizes the below awk script. # read_iso3166_table_awk=' # Variables that should be defined on the invocation line: # -v progname="progname" # -v uname_s="$(uname -s)" # BEGIN { lineno = 0 failed = 0 } function die(fmt, argc, argv) { printf "die \"%%s: %s\" \"%s\"", fmt, progname for (n = 1; n <= argc; n++) printf " \"%s\"", argv[n] print "" failed++ exit 1 } function add_country(tlc, name) { if (country_name[tlc]) { argv[1] = lineno argv[2] = tlc argv[3] = name die(FILENAME \ ":%d: country code \\`%s'\'' multiply defined: %s", 3, argv) } country_name[tlc] = name } function print_country_name(tlc) { name = country_name[tlc] gsub(/"/, "\\\"", name) printf "country_%s_name=\"%s\"\n", tlc, name printf "export country_%s_name\n", tlc } /^#/ { lineno++ next } !/^#/ { lineno++ # Split the current record (on TAB) into an array split($0, line, /\t/) # Get the ISO3166-1 (Alpha 1) 2-letter country code tlc = line[1] # # Validate the two-character country code # if (length(tlc) != 2) { argv[1] = lineno die(FILENAME ":%d: invalid format", 1, argv) } if (!match(tlc, /^[A-Z][A-Z]$/)) { argv[1] = lineno argv[2] = tlc die(FILENAME ":%d: invalid code \\`%s'\''", 2, argv) } # # Calculate the substr start-position of the name # name_start = 0 n = 4 if (uname_s ~ /Linux|Darwin|CYGWIN_NT-.*/) n = 2 else if (FILENAME ~ /\.tab$/) n = 2 while (--n) { # # Validate field-length of 2nd/3rd columns while we are here # if (n > 1 && length(line[n]) != 3) { argv[1] = lineno die(FILENAME ":%d: invalid format", 1, argv) } name_start += length(line[n]) + 1 } # Get the name field name = substr($0, name_start + 1) add_country(tlc, name) } END { list = "" for (tlc in country_name) { list = list (length(list) > 0 ? " " : "") tlc print_country_name(tlc) } printf "COUNTRIES=\"%s\"\n", list print "export COUNTRIES" } ' read_iso3166_table() { eval $( awk -v progname="$progname" \ -v uname_s="$UNAME_S" \ "$read_iso3166_table_awk" \ "$_PATH_ISO3166" ) } # read_zones # # Read the zone descriptions database in _PATH_ZONETAB: # /usr/share/zoneinfo/zone.tab on all OSes # # The format of this file (on all OSes) is: # code coordinates TZ comments # # With each of the following elements (described below) being separated by a # single tab character: # # code # The ISO 3166 2-character country code. # coordinates # Latitude and logitude of the zone's principal location in ISO # 6709 sign-degrees-minutes-seconds format, either +-DDMM+-DDDMM # or +-DDMMSS+-DDDMMSS, first latitude (+ is north), then long- # itude (+ is east). # TZ # Zone name used in value of TZ environment variable. # comments # Comments; present if and only if the country has multiple rows. # # Required variables [from the CONFIGURATION section (above)]: # # CONTINENTS # Space-separated list of continents. # continent_*_name # Directory element in _PATH_ZONEINFO for the continent # represented by *. # # Required variables [created by read_iso3166_table]: # # country_CODE_name # Country name of the country represented by CODE, the 2- # character country code. # # Variables created by this function: # # country_CODE_nzones # Either set to `-1' to indicate that the 2-character country # code has only a single zone associated with it (and therefore # you should query the `country_CODE_*' environment variables), # or set to `0' or higher to indicate how many zones are assoc- # iated with the given country code. When multiple zones are # configured for a single code, you should instead query the # `country_CODE_*_N' environment variables (e.g., `echo # $country_AQ_descr_1' prints the description of the first # timezone in Antarctica). # country_CODE_filename # The ``filename'' portion of the TZ value that appears after the # `/' (e.g., `Hong_Kong' from `Asia/Hong_Kong' or `Isle_of_Man' # from `Europe/Isle_of_Man'). # country_CODE_cont # The ``continent'' portion of the TZ value that appears before # the `/' (e.g., `Asia' from `Asia/Hong_Kong' or `Europe' from # `Europe/Isle_of_Man'). # country_CODE_descr # The comments associated with the ISO 3166 code entry (if any). # # NOTE: CODE is the 2-character country code. # # This function is a two-parter. Below is the awk(1) portion of the function, # afterward is the sh(1) function which utilizes the below awk script. # read_zones_awk=' # Variables that should be defined on the invocation line: # -v progname="progname" # BEGIN { lineno = 0 failed = 0 # # Initialize continents array/map (name => id) # split(ENVIRON["CONTINENTS"], array, /[[:space:]]+/) for (item in array) { cont = array[item] if (!cont) continue name = ENVIRON["continent_" cont "_name"] continents[name] = cont } } function die(fmt, argc, argv) { printf "die \"%%s: %s\" \"%s\"", fmt, progname for (n = 1; n <= argc; n++) printf " \"%s\"", argv[n] print "" failed++ exit 1 } function find_continent(name) { return continents[name] } function add_zone_to_country(lineno, tlc, descr, file, cont) { # # Validate the two-character country code # if (!match(tlc, /^[A-Z][A-Z]$/)) { argv[1] = lineno argv[2] = tlc die(FILENAME ":%d: country code \\`%s'\'' invalid", 2, argv) } if (!ENVIRON["country_" tlc "_name"]) { argv[1] = lineno argv[2] = tlc die(FILENAME ":%d: country code \\`%s'\'' unknown", 2, argv) } # # Add Zone to an array that we will parse at the end # if (length(descr) > 0) { if (country_nzones[tlc] < 0) { argv[1] = lineno die(FILENAME ":%d: conflicting zone definition", 1, argv) } n = ++country_nzones[tlc] country_cont[tlc,n] = cont country_filename[tlc,n] = file country_descr[tlc,n] = descr } else { if (country_nzones[tlc] > 0) { argv[1] = lineno die(FILENAME ":%d: zone must have description", 1, argv) } if (country_nzones[tlc] < 0) { argv[1] = lineno die(FILENAME ":%d: zone multiply defined", 1, argv) } country_nzones[tlc] = -1 country_cont[tlc] = cont country_filename[tlc] = file } } function print_country_code(tlc) { nz = country_nzones[tlc] printf "country_%s_nzones=%d\n", tlc, nz printf "export country_%s_nzones\n", tlc if (nz < 0) { printf "country_%s_cont=\"%s\"\n", tlc, country_cont[tlc] printf "export country_%s_cont\n", tlc printf "country_%s_filename=\"%s\"\n", tlc, country_filename[tlc] } else { n = 0 while ( ++n <= nz ) { printf "country_%s_cont_%d=\"%s\"\n", tlc, n, country_cont[tlc,n] printf "export country_%s_cont_%d\n", tlc, n printf "country_%s_filename_%d=\"%s\"\n", tlc, n, country_filename[tlc,n] printf "country_%s_descr_%d=\"%s\"\n", tlc, n, country_descr[tlc,n] } } } /^#/ { lineno++ next } !/^#/ { lineno++ # # Split the current record (on TAB) into an array # if (split($0, line, /\t/) < 2) { argv[1] = lineno die(FILENAME ":%d: invalid format", 1, argv) } # Get the ISO3166-1 (Alpha 1) 2-letter country code tlc = line[1] # # Validate the two-character country code # if (length(tlc) != 2) { argv[1] = lineno argv[2] = tlc die(FILENAME ":%d: invalid country code \\`%s'\''", 2, argv) } # Get the TZ field tz = line[3] # # Validate the TZ field # if (!match(tz, "/")) { argv[1] = lineno argv[2] = tz die(FILENAME ":%d: invalid zone name \\`%s'\''", 2, argv) } # # Get the continent portion of the TZ field # contbuf = tz sub("/.*$", "", contbuf) # # Validate the continent # cont = find_continent(contbuf) if (!cont) { argv[1] = lineno argv[2] = contbuf die(FILENAME ":%d: invalid region \\`%s'\''", 2, argv) } # # Get the filename portion of the TZ field # filename = tz sub("^[^/]*/", "", filename) # # Calculate the substr start-position of the comment # descr_start = 0 n = 4 while (--n) descr_start += length(line[n]) + 1 # Get the comment field descr = substr($0, descr_start + 1) add_zone_to_country(lineno, tlc, descr, filename, cont) } END { if (failed) exit failed for (tlc in country_nzones) print_country_code(tlc) } ' read_zones() { eval $(awk -v progname="$progname" "$read_zones_awk" "$_PATH_ZONETAB") } # sort_countries # # Sorts alphabetically the 2-character country codes listed in $COUNTRIES based # on the name of each country. # sort_countries_awk=' { split($0, array, /[[:space:]]+/) for (item in array) { tlc = array[item] print ENVIRON["country_" tlc "_name"] " " tlc } } ' sort_countries() { COUNTRIES=$( echo "$COUNTRIES" | awk "$sort_countries_awk" | sort | awk '{print $NF}' ) export COUNTRIES } # make_menus # # Creates the tag/item ordered-pair list environment variables for the # continent and country menus. # # Required variables [from the CONFIGURATION section (above)]: # # CONTINENTS # Space-separated list of continents. # continent_*_title # Desired menu text for the continent represented by *. # # Required variables [created by read_iso3166_table]: # # COUNTRIES # Space-separated list of 2-character country codes. # country_*_name :: when country_*_nzones < 0 # Desired menu text for the country-zone represented by *, the 2- # character country code. # # Required variables [created by read_zones]: # # country_*_nzones # Number of zones for the country represented by *, the 2- # character country code. Should be -1 if the country has only # one single zone, otherwise 1 or greater to indicate how many # zones the country has. # country_*_cont :: when country_*_nzones < 0 # Principal continent (or ocean) in which the country-zone # represented by *, the 2-character country code, resides. # country_*_cont_N :: when country_*_nzones > 0 # Principal continent (or ocean) in which zone-N of the country # represented by * resides, the 2-character country code. # country_*_descr_N :: when country_*_nzones > 0 # Desired submenu text for zone-N of the country represented by # *, the 2-character country code. # # Variables created by this function: # # continent_menu_list # Menu-list of continents. # continent_*_nitems # Number of items associated with the continent represented by *, # the continent identifier. # continent_*_tlc_N # 2-character country code of the Nth item in the continent menu # for the continent represented by *, the continent identifier. # continent_*_menu_list # Menu-list of countries/zones for each continent represented by # *, the continent identifier. # country_*_menu_list # For countries that have multiple zones, this is the submenu- # list of zones for said country represented by *, the 2- # character country code. # make_menus_awk=' function add_zone_n_to_country_menu(tlc, n) { zone_title = ENVIRON["country_" tlc "_descr_" n] gsub(/'\''/, "'\''\\'\'''\''", zone_title) country_menu_list[tlc] = country_menu_list[tlc] \ ( length(country_menu_list[tlc]) > 0 ? "\n" : "" ) \ n " '\''" zone_title "'\''" } BEGIN { # # 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. # i = split(ENVIRON["COUNTRIES"], countries, /[[:space:]]+/) for (cp = 1; cp <= i; cp++) { tlc = countries[cp] title = ENVIRON["country_" tlc "_name"] gsub(/'\''/, "'\''\\'\'''\''", title) nzones = ENVIRON["country_" tlc "_nzones"] if (!nzones) { # Country has no zones continue } else if (nzones < 0) { # Country has only one zone cont = ENVIRON["country_" tlc "_cont"] nitems = ++continent_nitems[cont] continent_tlc[cont,nitems] = tlc continent_title[cont,nitems] = title } else { # Country has one or more zones for (n = 1; n <= nzones; n++) { add_zone_n_to_country_menu(tlc, n) cont = ENVIRON["country_" tlc "_cont_" n] for (x = 1; x < n; x++) { contx = ENVIRON["country_"tlc"_cont_"x] if (cont == contx) break } if (x == n) { nitems = ++continent_nitems[cont] continent_tlc[cont,nitems] = tlc continent_title[cont,nitems] = title } } } } } END { tags = ENVIRON["DIALOG_MENU_TAGS"] cont_menu_list = "" tagn = 0 # # Assemble the menu items in the menu list for each continent/ocean. # i = split(ENVIRON["CONTINENTS"], array, /[[:space:]]+/) for (item = 1; item <= i; item++) { cont = array[item] if (!cont) continue if (++tagn >= length(tags)) break tag = substr(tags, tagn, 1) cont_menu_list = cont_menu_list \ ( length(cont_menu_list) > 0 ? "\n" : "" ) \ "'\''" tag "'\'' '\''" \ ENVIRON["continent_" cont "_title"] "'\''" nitems = continent_nitems[cont] printf "continent_%s_nitems=%d\n", cont, nitems menu_list = "" for (n = 1; n <= nitems; n++) { printf "continent_%s_tlc_%d=%s\n", cont, n, continent_tlc[cont,n] title = continent_title[cont,n] menu_list = menu_list \ ( length(menu_list) > 0 ? "\n" : "" ) \ n " '\''" title "'\''" } gsub(/"/, "\\\"", menu_list) printf "continent_%s_menu_list=\"%s\"\n", cont, menu_list } gsub(/"/, "\\\"", continent_menu_list) printf "continent_menu_list=\"%s\"\n", cont_menu_list print "export continent_menu_list" # # Dump the submenus of countries with multiple zones # for (tlc in country_menu_list) { menu_list = country_menu_list[tlc] gsub(/"/, "\\\"", menu_list) printf "country_%s_menu_list=\"%s\"\n", tlc, menu_list } } ' make_menus() { eval $( :| awk "$make_menus_awk" ) } # OCEANP $cont # # Returns "1" if the first argument is an ocean, otherwise NULL. # OCEANP() { case "$1" in arctic|atlantic|indian|pacific) echo 1 esac } # find_continent $title # # Returns continent identifier given continent title. # find_continent() { local cont for cont in $CONTINENTS; do if [ "$1" = "$( continent $cont title )" ]; then echo "$cont" return $SUCCESS fi done return $FAILURE } # continent $cont $property # # Returns a single property of a given continent. Available properties are: # # name Directory name of continent/ocean as it appears in # _PATH_ZONEINFO. # title Menu text of this continent/ocean to be displayed in the # continent-selection menu. # nitems Number of submenu items associated with this # continent/ocean. # tlc_N 2-character country code of the Nth submenu item associated # with this continent displayed in the country-selection menu # (which appears after continent selection). # menu_list Menu-list of regions for this continent. # continent() { local cont="$1" property="$2" eval echo \"\${continent_${cont}_$property}\" } # country $code $property # # Returns a single property of a given country. Available properties are: # # name Name of the country as read from _PATH_ISO3166. # nzones Number of zones within the country (-1 if country has # only a single zone). # filename The filename portion of the TZ field (after the `/') as # read from _PATH_ZONETAB. # cont The principal continent in which the country lies (appears # before the `/' in the TZ field of _PATH_ZONETAB). # filename_N Like filename, but for the Nth zone when the country has # multiple zones (nzones > 0). # cont_N Like cont, but for the Nth zone when the country has # multiple zones (nzones > 0). # descr_N Like name, but for the Nth zone when the country has # multiple zones (nzones > 0) # country() { local code="$1" property="$2" eval echo \"\${country_${code}_$property}\" } # install_zone_file $filename # # Installs a zone file to _PATH_LOCALTIME. # install_zone_file() { local filename="$1" local copymode title msg err size if [ ! -e "$_PATH_LOCALTIME" ]; then # Nothing there yet... copymode=1 elif [ -L "$_PATH_LOCALTIME" ]; then copymode= else copymode=1 fi if [ "$VERBOSE" ]; then if [ "$copymode" ]; then msg="Copying $filename to $_PATH_LOCALTIME" else msg="Creating symbolic link $_PATH_LOCALTIME" msg="$msg to $filename" fi size=$( dialog_infobox_size "" "$msg" ) eval $DIALOG \ ${USE_XDIALOG:+--no-buttons} \ --infobox "'$msg'" $size fi if [ "$REALLYDOIT" ]; then title="Error" err=$( rm -f $_PATH_LOCALTIME 2>&1 ) if [ "$err" ]; then size=$( dialog_buttonbox_size "$title" "$err" ) eval $DIALOG --title "'$title'" --msgbox "'$err'" $size return $FAILURE fi if [ "$copymode" ]; then err=$( umask 222 && :> "$_PATH_LOCALTIME" ) if [ "$err" ]; then size=$( dialog_buttonbox_size "$title" "$err" ) eval $DIALOG --title "'$title'" \ --msgbox "'$err'" $size return $FAILURE fi err=$( cat "$filename" > "$_PATH_LOCALTIME" ) if [ "$err" ]; then size=$( dialog_buttonbox_size "$title" "$err" ) eval $DIALOG --title "'$title'" \ --msgbox "'$err'" $size return $FAILURE fi else err=$( ln -s "$filename" "$_PATH_LOCALTIME" ) if [ "$err" ]; then size=$( dialog_buttonbox_size "$title" "$err" ) eval $DIALOG --title "'$title'" \ --msgbox "'$err'" $size return $FAILURE fi fi fi if [ "$VERBOSE" ]; then title="Done" if [ "$copymode" ]; then msg="Copied timezone file from $filename" msg="$msg to $_PATH_LOCALTIME" else msg="Created symbolic link from $_PATH_LOCALTIME" msg="$msg to $filename" fi size=$( dialog_buttonbox_size "$title" "$msg" ) eval $DIALOG --title "'$title'" --msgbox "'$msg'" $size fi return $SUCCESS } ############################################################################### ############################ DIALOG SIZE FUNCTIONS ############################ ############################################################################### # dialog_infobox_size $title $prompt # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--infobox' boxes sensibly. # # This function helps solve this issue by taking as arguments (in order of # appearance) the title and prompt, returning the optimal width and height for # the box (not exceeding the actual terminal width or height). # # Output is in the format of "height width". # dialog_infobox_size() { local title="$1" prompt="$2" n=0 local min_width max_size if [ "$USE_XDIALOG" ]; then min_width=35 max_size="$XDIALOG_MAX_SIZE" # see CONFIGURATION else min_width=24 min_rows=0 max_size="$( stty size )" # usually "24 80" fi local max_height="${max_size%%[$IFS]*}" local max_width="${max_size##*[$IFS]}" local height width=$min_width # # Bump width for long titles (but don't exceed terminal width). # n=$(( ${#title} + 4 )) if [ $n -gt $width -a $n -gt $min_width ]; then # Add 16.6% width for Xdialog(1) [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) if [ $n -lt $max_width ]; then width=$n else width=$max_width fi fi # # Bump width for long prompts (if not already at maximum width). # if [ $width -lt $max_width ]; then n=$( echo "$prompt" | longest_line_length ) n=$(( $n + 4 )) # Add 16.6% width for Xdialog(1) [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) if [ $n -gt $width -a $n -gt $min_width ]; then if [ $n -lt $max_width ]; then width=$n else width=$max_width fi fi fi # # Set height based on number of rows in prompt # height=$( echo "$prompt" | awk 'END { print NR }' ) height=$(( $height + 2 )) # Return both echo "$height $width" } # dialog_buttonbox_size $title $prompt # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--msgbox' and `--yesno' boxes sensibly. # # This function helps solve this issue by taking as arguments (in order of # appearance) the title and prompt, returning the optimal width and height for # the box (not exceeding the actual terminal width or height). # # Output is in the format of "height width". # dialog_buttonbox_size() { local title="$1" prompt="$2" local size="$( dialog_infobox_size "$title" "$prompt" )" local height="${size%%[$IFS]*}" local width="${size##*[$IFS]}" # Add height to accomodate the buttons height=$(( $height + 3 )) # Adjust for clipping with Xdialog(1) on Linux/GTK2 [ "$USE_XDIALOG" ] && height=$(( $height + 1 )) # Return both echo "$height $width" } # dialog_menu_size $title $prompt $tag1 $item1 $tag2 $item2 ... # # Not all versions of dialog(1) perform auto-sizing of the width and height of # `--menu' boxes sensibly. # # This function helps solve this issue by taking as arguments (in order of # appearance) the title, prompt, and list of tag/item pairs, returning the # optimal width and height for the menu (not exceeding the actual terminal # width or height). # # Output is in the format of "height width rows". # dialog_menu_size() { local title="$1" prompt="$2" n=0 local min_width min_rows max_size if [ "$USE_XDIALOG" ]; then min_width=35 min_rows=1 max_size="$XDIALOG_MAX_SIZE" # see CONFIGURATION else min_width=24 min_rows=0 max_size="$( stty size )" # usually "24 80" fi local max_width="${max_size##*[$IFS]}" local max_height="${max_size%%[$IFS]*}" local box_size="$( dialog_infobox_size "$title" "$prompt" )" local box_height="${box_size%%[$IFS]*}" local box_width="${box_size##*[$IFS]}" local max_rows=$(( $max_height - 8 )) local height width=$box_width rows=$min_rows shift 2 # title/prompt # # Bump width for long menu items (if not already at maximum width) and # calculate the number of rows (not to exceed terminal maximum height). # while [ $# -ge 2 ]; do local tag="$1" item="$2" shift 2 # tag/item if [ $width -lt $max_width ]; then n=$(( ${#tag} + ${#item} + 11 )) # Add 16.6% width for Xdialog(1) [ "$USE_XDIALOG" ] && n=$(( $n + $n / 6 )) if [ $n -gt $width -a $n -gt $min_width ]; then if [ $n -lt $max_width ]; then width=$n else width=$max_width fi fi fi if [ $rows -lt $max_rows ]; then rows=$(( $rows + 1 )) fi done # Fix rows and set height [ $rows -gt 0 ] || rows=1 if [ "$USE_XDIALOG" ]; then height=$(( $rows + $box_height + 7 )) else height=$(( $rows + $box_height + 4 )) fi # Return all three echo "$height $width $rows" } ############################################################################### ########################### DIALOG YESNO FUNCTIONS ############################ ############################################################################### # confirm_zone $filename # # Prompt the user to confirm the new timezone data. The first (and only) # argument should be the pathname to the zoneinfo file, either absolute or # relative to `/usr/share/zoneinfo' (e.g., "America/Los_Angeles"). # # The return status is 0 if "Yes" is chosen, 1 if "No", and 255 if Esc is # pressed (see dialog(1) for additional details). # confirm_zone() { local filename="$1" local title="Confirmation" local tm_zone="$( TZ="$filename" date +%Z )" local prompt="Does the abbreviation \`$tm_zone' look reasonable?" local height=5 width=72 [ "$UNAME_S" = "FreeBSD" -a ! "$USE_XDIALOG" ] && height=4 $DIALOG --title "$title" --yesno "$prompt" $height $width } ############################################################################### ############################ DIALOG MENU FUNCTIONS ############################ ############################################################################### # dialog_menutag # # Obtain the menutag chosen by the user from the most recently displayed # dialog(1) menu and clean up any temporary files. # dialog_menutag() { local tmpfile="$DIALOG_TMPDIR/dialog.menu.$$" [ -f "$tmpfile" ] || return $FAILURE cat "$tmpfile" 2> /dev/null quietly rm -f "$tmpfile" return $SUCCESS } # dialog_menutag2item $tag_chosen $tag1 $item1 $tag2 $item2 ... # # To use the `--menu' option of dialog(1) you must pass an ordered list of # tag/item pairs on the command-line. When the user selects a menu option the # tag for that item is printed to stderr. # # This function allows you to dereference the tag chosen by the user back into # the item associated with said tag. # # Pass the tag chosen by the user as the first argument, followed by the # ordered list of tag/item pairs (HINT: use the same tag/item list as was # passed to dialog(1) for consistency). # # If the tag cannot be found, NULL is returned. # dialog_menutag2item() { local tag="$1" tagn item shift 1 while [ $# -gt 0 ]; do tagn="$1" item="$2" shift 2 if [ "$tag" = "$tagn" ]; then echo "$item" return $SUCCESS fi done return $FAILURE } # dialog_menu_root # # Display the dialog(1)-based application root menu. # dialog_menu_root() { local title="Time Zone Selector" local prompt="Select a region" local size size=$( eval dialog_menu_size "'$title'" "'$prompt'" \ $continent_menu_list ) eval $DIALOG \ --title "'$title'" \ --menu "'$prompt'" $size \ $continent_menu_list \ 2> "$DIALOG_TMPDIR/dialog.menu.$$" } ############################################################################### ################################# MAIN SOURCE ################################# ############################################################################### [ "`id -u`" = "0" ] || die "Must run as root!" # # Process command-line arguments # while getopts nsvX flag; do case "$flag" in n) REALLYDOIT=;; s) SKIPUTC=1;; v) VERBOSE=1;; X) USE_XDIALOG=1;; \?) usage;; esac done shift $(( $OPTIND - 1 )) # # Trap signals so we can recover gracefully # trap 'die' SIGINT SIGTERM SIGPIPE SIGXCPU SIGXFSZ \ SIGFPE SIGTRAP SIGABRT SIGSEGV [ "$SHELL" = "$0" ] && trap 'die' SIGSTOP SIGTSTP SIGTTIN SIGTTOU trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM # # Process `-X' command-line option # if [ "$USE_XDIALOG" ]; then DIALOG=Xdialog # Disable the use of `--defaultno' since Xdialog(1) has it's own # `--dialog-no' option which will be triggered by $USE_XDIALOG # DIALOG_ENABLE_DEFAULTNO= fi # # DIALOG fixup (FreeBSD-9.0 dialog(1) and Linux dialog(1) support the # `--defaultno' command-line argument. # if [ "$CHECK_DIALOG_FOR_DEFAULTNO" -a ! "$USE_XDIALOG" ]; then have $DIALOG || die "%s: %s: No such file or directory" "$progname" "$DIALOG" DIALOG=$( which $DIALOG ) data=$( strings "$DIALOG" ) || die if echo "$data" | grep -q defaultno; then DIALOG_ENABLE_DEFAULTNO=1 else DIALOG_ENABLE_DEFAULTNO= fi fi # # Process the UTC option # if [ "$_PATH_WALL_CMOS_CLOCK" -a ! "$SKIPUTC" ]; then title="Select local or UTC (Greenwhich Mean Time) clock" msg="Is this machine's CMOS clock set to UTC? " msg="$msg If it is set to local time,\nor you" msg="$msg don't know, please choose NO here!" if [ "$USE_XDIALOG" ]; then height=7 width=77 else height=7 width=72 # If dialog(1) supports --defaultno, then it also has a bug # in which it counts the newline in the wrapping. A simple # fix is to ``add one'' when using such a beast. # # Includes Linux, FreeBSD 9 (but not FreeBSD 8.1 or older) # and possibly others -- anyone using cdialog really. # [ "$DIALOG_ENABLE_DEFAULTNO" ] && width=73 fi $DIALOG --title "$title" \ ${DIALOG_ENABLE_DEFAULTNO:+--defaultno} \ ${USE_XDIALOG:+--default-no} \ --yesno "$msg" $height $width result=$? if [ $result -eq 0 ]; then # User chose YES [ "$REALLYDOIT" ] && quietly rm -f "$_PATH_WALL_CMOS_CLOCK" else # User chose NO, pressed ESC (or Ctrl-C), or closed box [ "$REALLYDOIT" ] && ( umask 222 && :> "$_PATH_WALL_CMOS_CLOCK" ) fi fi # # Process optional default zone argument # if [ $# -ge 1 ]; then default="$1" $DIALOG --title "Default timezone provided" \ --yesno "Use the default \`$default' zone?" -1 -1 result=$? if [ $result -eq 0 ]; then # User chose YES install_zone_file "$default" else # User chose NO, pressed ESC (or Ctrl-C), or closed box : nothing fi exit $result fi # # Read databases and perform initialization # read_iso3166_table # creates $COUNTRIES and $country_*_name read_zones # creates $country_*_{descr,cont,filename} sort_countries # sorts the countries listed for each continent make_menus # creates $continent_menu_list and $continent_*_menu_list # # Launch application root menu # NEED_CONTINENT=1 NEED_COUNTRY=1 while :; do if [ "$NEED_CONTINENT" ]; then dialog_menu_root # prompt the user to select a continent/ocean retval=$? mtag=$( dialog_menutag ) if [ $retval -ne 0 ]; then [ ! "$USE_XDIALOG" ] && $DIALOG --clear [ $retval -ne 1 ] && die exit $SUCCESS fi NEED_CONTINENT= continent=$( eval dialog_menutag2item "'$mtag'" \ $continent_menu_list ) cont=$( find_continent "$continent" ) cont_title=$( continent $cont title ) nitems=$( continent $cont nitems ) isocean=$( OCEANP $cont ) fi if [ "$NEED_COUNTRY" ]; then # # Short cut -- if there's only one country, don't post a menu. # if [ $nitems -eq 1 ]; then tag=1 else # # It's amazing how much good grammar really matters... # if [ ! "$isocean" ]; then title="Countries in $cont_title" prompt="Select a country" else title="Islands and groups in the $cont_title" prompt="Select an island or group" fi # # Calculate size of menu # menu_list=$( continent $cont menu_list ) size=$( eval dialog_menu_size "'$title'" "'$prompt'" \ $menu_list ) # # Launch the country selection menu # eval $DIALOG \ --title "'$title'" \ --menu "'$prompt'" $size \ $menu_list \ 2> "$DIALOG_TMPDIR/dialog.menu.$$" retval=$? tag=$( dialog_menutag ) if [ $retval -ne 0 ]; then NEED_CONTINENT=1 continue # back to main menu fi fi # Get the country code from the user's selection tlc=$( continent $cont tlc_$tag ) NEED_COUNTRY= fi # # If the selection has only one zone (nzones == -1), # just set it. # nzones=$( country $tlc nzones ) if [ $nzones -lt 0 ]; then real_cont=$( country $tlc cont ) real_continent=$( continent $real_cont name ) name=$( country $tlc name ) filename=$( country $tlc filename ) if ! confirm_zone "$real_continent/$filename"; then [ $nitems -eq 1 ] && NEED_CONTINENT=1 NEED_COUNTRY=1 continue fi else title="$( country $tlc name ) Time Zones" prompt="Select a zone which observes the same" prompt="$prompt time as your locality." menu_list=$( country $tlc menu_list ) size=$( eval dialog_menu_size "'$title'" "'$prompt'" \ $menu_list ) # # Launch the zone selection menu # NOTE: This is as deep as we go # eval $DIALOG \ --title "'$title'" \ --menu "'$prompt'" $size \ $menu_list \ 2> "$DIALOG_TMPDIR/dialog.menu.$$" retval=$? n=$( dialog_menutag ) if [ $retval -ne 0 ]; then [ $nitems -eq 1 ] && NEED_CONTINENT=1 NEED_COUNTRY=1 continue fi real_cont=$( country $tlc cont_$n ) real_continent=$( continent $real_cont name ) name=$( country $tlc name ) filename=$( country $tlc filename_$n ) confirm_zone "$real_continent/$filename" || continue fi [ $retval -eq 0 ] || continue # back to main menu if ! install_zone_file "$_PATH_ZONEINFO/$real_continent/$filename" then [ $nzones -lt 0 ] && NEED_COUNTRY=1 else break fi done ################################################################################ # END ################################################################################ # # $Header$ # # $Copyright: 2011 Devin Teske. All Rights Reserved. $ # # $Log$ # ################################################################################