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