xref: /freebsd/usr.sbin/bsdconfig/timezone/share/zones.subr (revision 0b3105a37d7adcadcb720112fed4dc4e8040be99)
1if [ ! "$_TIMEZONE_ZONES_SUBR" ]; then _TIMEZONE_ZONES_SUBR=1
2#
3# Copyright (c) 2011-2012 Devin Teske
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD$
28#
29############################################################ INCLUDES
30
31BSDCFG_SHARE="/usr/share/bsdconfig"
32. $BSDCFG_SHARE/common.subr || exit 1
33f_dprintf "%s: loading includes..." timezone/zones.subr
34f_include $BSDCFG_SHARE/dialog.subr
35f_include $BSDCFG_SHARE/strings.subr
36f_include $BSDCFG_SHARE/timezone/continents.subr
37
38BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="090.timezone"
39f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
40
41############################################################ CONFIGURATION
42
43#
44# Standard pathnames
45#
46_PATH_ZONETAB="/usr/share/zoneinfo/zone.tab"
47_PATH_ZONEINFO="/usr/share/zoneinfo"
48_PATH_LOCALTIME="/etc/localtime"
49_PATH_DB="/var/db/zoneinfo"
50
51#
52# Export required i18n messages for awk(1) ENVIRON visibility
53#
54export msg_conflicting_zone_definition
55export msg_country_code_invalid
56export msg_country_code_unknown
57export msg_invalid_country_code
58export msg_invalid_format
59export msg_invalid_region
60export msg_invalid_zone_name
61export msg_zone_multiply_defined
62export msg_zone_must_have_description
63
64############################################################ FUNCTIONS
65
66# f_read_zones
67#
68# Read the zone descriptions database in _PATH_ZONETAB:
69# 	/usr/share/zoneinfo/zone.tab on all OSes
70#
71# The format of this file (on all OSes) is:
72# 	code	coordinates	TZ	comments
73#
74# With each of the following elements (described below) being separated by a
75# single tab character:
76#
77# 	code
78# 		The ISO 3166 2-character country code.
79# 	coordinates
80# 		Latitude and logitude of the zone's principal location in ISO
81# 		6709 sign-degrees-minutes-seconds format, either +-DDMM+-DDDMM
82# 		or +-DDMMSS+-DDDMMSS, first latitude (+ is north), then long-
83# 		itude (+ is east).
84# 	TZ
85# 		Zone name used in value of TZ environment variable.
86# 	comments
87# 		Comments; present if and only if the country has multiple rows.
88#
89# Required variables [from continents.subr]:
90#
91# 	CONTINENTS
92# 		Space-separated list of continents.
93# 	continent_*_name
94# 		Directory element in _PATH_ZONEINFO for the continent
95# 		represented by *.
96#
97# Required variables [created by f_read_iso3166_table from iso3166.subr]:
98#
99# 	country_CODE_name
100# 		Country name of the country represented by CODE, the 2-
101# 		character country code.
102#
103# Variables created by this function:
104#
105# 	country_CODE_nzones
106# 		Either set to `-1' to indicate that the 2-character country
107# 		code has only a single zone associated with it (and therefore
108# 		you should query the `country_CODE_*' environment variables),
109# 		or set to `0' or higher to indicate how many zones are assoc-
110# 		iated with the given country code. When multiple zones are
111# 		configured for a single code, you should instead query the
112# 		`country_CODE_*_N' environment variables (e.g., `echo
113# 		$country_AQ_descr_1' prints the description of the first
114# 		timezone in Antarctica).
115# 	country_CODE_filename
116# 		The ``filename'' portion of the TZ value that appears after the
117# 		`/' (e.g., `Hong_Kong' from `Asia/Hong_Kong' or `Isle_of_Man'
118# 		from `Europe/Isle_of_Man').
119# 	country_CODE_cont
120# 		The ``continent'' portion of the TZ value that appears before
121# 		the `/' (e.g., `Asia' from `Asia/Hong_Kong' or `Europe' from
122# 		`Europe/Isle_of_Man').
123# 	country_CODE_descr
124# 		The comments associated with the ISO 3166 code entry (if any).
125#
126# 	NOTE: CODE is the 2-character country code.
127#
128# This function is a two-parter. Below is the awk(1) portion of the function,
129# afterward is the sh(1) function which utilizes the below awk script.
130#
131f_read_zones_awk='
132# Variables that should be defined on the invocation line:
133# 	-v progname="progname"
134#
135BEGIN {
136	lineno = 0
137	failed = 0
138
139	#
140	# Initialize continents array/map (name => id)
141	#
142	split(ENVIRON["CONTINENTS"], array, /[[:space:]]+/)
143	for (item in array)
144	{
145		cont = array[item]
146		if (!cont) continue
147		name = ENVIRON["continent_" cont "_name"]
148		continents[name] = cont
149	}
150}
151function die(fmt, argc, argv)
152{
153	printf "f_die 1 \"%%s: %s\" \"%s\"", fmt, progname
154	for (n = 1; n <= argc; n++)
155		printf " \"%s\"", argv[n]
156	print ""
157	failed++
158	exit 1
159}
160function find_continent(name)
161{
162	return continents[name]
163}
164function add_zone_to_country(lineno, tlc, descr, file, cont)
165{
166	#
167	# Validate the two-character country code
168	#
169	if (!match(tlc, /^[A-Z][A-Z]$/))
170	{
171		argv[1] = FILENAME
172		argv[2] = lineno
173		argv[3] = tlc
174		die(ENVRION["msg_country_code_invalid"], 3, argv)
175	}
176	if (!ENVIRON["country_" tlc "_name"])
177	{
178		argv[1] = FILENAME
179		argv[2] = lineno
180		argv[3] = tlc
181		die(ENVIRON["msg_country_code_unknown"], 3, argv)
182	}
183
184	#
185	# Add Zone to an array that we will parse at the end
186	#
187	if (length(descr) > 0)
188	{
189		if (country_nzones[tlc] < 0)
190		{
191			argv[1] = FILENAME
192			argv[2] = lineno
193			die(ENVIRON["msg_conflicting_zone_definition"], 2, argv)
194		}
195
196		n = ++country_nzones[tlc]
197		country_cont[tlc,n] = cont
198		country_filename[tlc,n] = file
199		country_descr[tlc,n] = descr
200	}
201	else
202	{
203		if (country_nzones[tlc] > 0)
204		{
205			argv[1] = FILENAME
206			argv[2] = lineno
207			die(ENVIRON["msg_zone_must_have_description"], 2, argv)
208		}
209		if (country_nzones[tlc] < 0)
210		{
211			argv[1] = FILENAME
212			argv[2] = lineno
213			die(ENVIRON["msg_zone_multiply_defined"], 2, argv)
214		}
215
216		country_nzones[tlc] = -1
217		country_cont[tlc] = cont
218		country_filename[tlc] = file
219	}
220}
221function print_country_code(tlc)
222{
223	nz = country_nzones[tlc]
224
225	printf "country_%s_nzones=%d\n", tlc, nz
226	printf "export country_%s_nzones\n", tlc
227
228	if (nz < 0)
229	{
230		printf "country_%s_cont=\"%s\"\n", tlc, country_cont[tlc]
231		printf "export country_%s_cont\n", tlc
232		printf "country_%s_filename=\"%s\"\n",
233		       tlc, country_filename[tlc]
234	}
235	else
236	{
237		n = 0
238		while ( ++n <= nz )
239		{
240			printf "country_%s_cont_%d=\"%s\"\n",
241			       tlc, n, country_cont[tlc,n]
242			printf "export country_%s_cont_%d\n", tlc, n
243			printf "country_%s_filename_%d=\"%s\"\n",
244			       tlc, n, country_filename[tlc,n]
245			printf "country_%s_descr_%d=\"%s\"\n",
246			       tlc, n, country_descr[tlc,n]
247		}
248	}
249}
250/^#/ {
251	lineno++
252	next
253}
254!/^#/ {
255	lineno++
256
257	#
258	# Split the current record (on TAB) into an array
259	#
260	if (split($0, line, /\t/) < 2)
261	{
262		argv[1] = FILENAME
263		argv[2] = lineno
264		die(ENVIRON["msg_invalid_format"], 2, argv)
265	}
266
267	# Get the ISO3166-1 (Alpha 1) 2-letter country code
268	tlc = line[1]
269
270	#
271	# Validate the two-character country code
272	#
273	if (length(tlc) != 2)
274	{
275		argv[1] = FILENAME
276		argv[2] = lineno
277		argv[3] = tlc
278		die(ENVIRON["msg_invalid_country_code"], 3, argv)
279	}
280
281	# Get the TZ field
282	tz = line[3]
283
284	#
285	# Validate the TZ field
286	#
287	if (!match(tz, "/"))
288	{
289		argv[1] = FILENAME
290		argv[2] = lineno
291		argv[3] = tz
292		die(ENVIRON["msg_invalid_zone_name"], 3, argv)
293	}
294
295	#
296	# Get the continent portion of the TZ field
297	#
298	contbuf = tz
299	sub("/.*$", "", contbuf)
300
301	#
302	# Validate the continent
303	#
304	cont = find_continent(contbuf)
305	if (!cont)
306	{
307		argv[1] = FILENAME
308		argv[2] = lineno
309		argv[3] = contbuf
310		die(ENVIRON["msg_invalid_region"], 3, argv)
311	}
312
313	#
314	# Get the filename portion of the TZ field
315	#
316	filename = tz
317	sub("^[^/]*/", "", filename)
318
319	#
320	# Calculate the substr start-position of the comment
321	#
322	descr_start = 0
323	n = 4
324	while (--n)
325		descr_start += length(line[n]) + 1
326
327	# Get the comment field
328	descr = substr($0, descr_start + 1)
329
330	add_zone_to_country(lineno, tlc, descr, filename, cont)
331}
332END {
333	if (failed) exit failed
334	for (tlc in country_nzones)
335		print_country_code(tlc)
336}
337'
338f_read_zones()
339{
340	eval $( awk -v progname="$pgm"   \
341	            "$f_read_zones_awk"  \
342	            "$_PATH_ZONETAB"     )
343}
344
345# f_install_zoneinfo_file $filename
346#
347# Installs a zone file to _PATH_LOCALTIME.
348#
349f_install_zoneinfo_file()
350{
351	local funcname=f_install_zoneinfo_file
352	local zoneinfo_file="$1"
353	local copymode title msg height width
354
355	if [ -L "$_PATH_LOCALTIME" ]; then
356		copymode=
357	elif [ ! -e "$_PATH_LOCALTIME" ]; then
358		# Nothing there yet...
359		copymode=1
360	else
361		copymode=1
362	fi
363
364	if [ "$VERBOSE" ]; then
365		if [ ! "$zoneinfo_file" ]; then
366			f_sprintf msg "$msg_removing_file" "$_PATH_LOCALTIME"
367		elif [ "$copymode" ]; then
368			f_sprintf msg "$msg_copying_file" \
369			              "$zoneinfo_file" "$_PATH_LOCALTIME"
370		else
371			f_sprintf msg "$msg_creating_symlink" \
372			              "$_PATH_LOCALTIME" "$zoneinfo_file"
373		fi
374		if [ "$USEDIALOG" ]; then
375			f_dialog_title "$msg_info"
376			f_dialog_msgbox "$msg"
377			f_dialog_title_restore
378		else
379			printf "%s\n" "$msg"
380		fi
381	fi
382
383	[ "$REALLYDOIT" ] || return $SUCCESS
384
385	local catch_args="-de"
386	[ "$USEDIALOG" ] && catch_args=
387
388	if [ ! "$zoneinfo_file" ]; then
389		f_eval_catch $catch_args $funcname rm \
390			'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
391		f_eval_catch $catch_args $funcname rm \
392			'rm -f "%s"' "$_PATH_DB" || return $FAILURE
393
394		if [ "$VERBOSE" ]; then
395			f_sprintf msg "$msg_removed_file" "$_PATH_LOCALTIME"
396			if [ "$USEDIALOG" ]; then
397				f_dialog_title "$msg_done"
398				f_dialog_msgbox "$msg"
399				f_dialog_title_restore
400			else
401				printf "%s\n" "$msg"
402			fi
403		fi
404		return $SUCCESS
405	fi # ! zoneinfo_file
406
407	if [ "$copymode" ]; then
408		f_eval_catch $catch_args $funcname rm \
409			'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
410		f_eval_catch $catch_args $funcname sh \
411			'umask 222 && :> "%s"' "$_PATH_LOCALTIME" ||
412			return $FAILURE
413		f_eval_catch $catch_args $funcname sh \
414			'cat "%s" > "%s"' \
415			"$zoneinfo_file" "$_PATH_LOCALTIME" || return $FAILURE
416	else
417		f_eval_catch $catch_args $funcname sh \
418			'( :< "%s" )' "$zoneinfo_file" || return $FAILURE
419		f_eval_catch $catch_args $funcname rm \
420			'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
421		f_eval_catch $catch_args $funcname ln \
422			'ln -s "%s" "%s"' \
423			"$zoneinfo_file" "$_PATH_LOCALTIME" || return $FAILURE
424	fi # copymode
425
426	if [ "$VERBOSE" ]; then
427		if [ "$copymode" ]; then
428			f_sprintf msg "$msg_copied_timezone_file" \
429			              "$zoneinfo_file" "$_PATH_LOCALTIME"
430		else
431			f_sprintf msg "$msg_created_symlink" \
432			              "$_PATH_LOCALTIME" "$zoneinfo_file"
433		fi
434		if [ "$USEDIALOG" ]; then
435			f_dialog_title "$msg_done"
436			f_dialog_msgbox "$msg"
437			f_dialog_title_restore
438		else
439			printf "%s\n" "$msg"
440		fi
441	fi
442
443	return $SUCCESS
444}
445
446# f_install_zoneinfo $zoneinfo
447#
448# Install a zoneinfo file relative to _PATH_ZONEINFO. The given $zoneinfo
449# will be written to _PATH_DB (usable later with the `-r' flag).
450#
451f_install_zoneinfo()
452{
453	local zoneinfo="$1"
454	local rv
455
456	f_install_zoneinfo_file "$_PATH_ZONEINFO/$zoneinfo"
457	rv=$?
458
459	# Save knowledge for later
460	if [ "$REALLYDOIT" -a $rv -eq $SUCCESS ]; then
461		if true 2> /dev/null > "$_PATH_DB"; then
462			cat <<-EOF > "$_PATH_DB"
463			$zoneinfo
464			EOF
465		fi
466	fi
467
468	return $rv
469}
470
471# f_confirm_zone $filename
472#
473# Prompt the user to confirm the new timezone data. The first (and only)
474# argument should be the pathname to the zoneinfo file, either absolute or
475# relative to `/usr/share/zoneinfo' (e.g., "America/Los_Angeles").
476#
477# The return status is 0 if "Yes" is chosen, 1 if "No", and 255 if Esc is
478# pressed (see dialog(1) for additional details).
479#
480f_confirm_zone()
481{
482	local filename="$1"
483	f_dialog_title "$msg_confirmation"
484	local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
485	f_dialog_title_restore
486	local tm_zone="$( TZ="$filename" date +%Z )"
487	local prompt # Calculated below
488	local height=5 width=72
489
490	f_sprintf prompt "$msg_look_reasonable" "$tm_zone"
491	if [ "$USE_XDIALOG" ]; then
492		height=$(( $height + 4 ))
493		$DIALOG \
494			--title "$title"         \
495			--backtitle "$btitle"    \
496			--ok-label "$msg_yes"    \
497			--cancel-label "$msg_no" \
498			--yesno "$prompt" $height $width
499	else
500		$DIALOG \
501			--title "$title"       \
502			--backtitle "$btitle"  \
503			--yes-label "$msg_yes" \
504			--no-label "$msg_no"   \
505			--yesno "$prompt" $height $width
506	fi
507}
508
509# f_set_zone_utc
510#
511# Resets to the UTC timezone.
512#
513f_set_zone_utc()
514{
515	f_confirm_zone "" || return $FAILURE
516	f_install_zoneinfo_file ""
517}
518
519############################################################ MAIN
520
521f_dprintf "%s: Successfully loaded." timezone/zones.subr
522
523fi # ! $_TIMEZONE_ZONES_SUBR
524