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