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_dprintf "%s: loading includes..." timezone/zones.subr
34f_include $BSDCFG_SHARE/dialog.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 zoneinfo_file="$1"
351	local copymode title msg err height width
352
353	if [ -L "$_PATH_LOCALTIME" ]; then
354		copymode=
355	elif [ ! -e "$_PATH_LOCALTIME" ]; then
356		# Nothing there yet...
357		copymode=1
358	else
359		copymode=1
360	fi
361
362	if [ "$VERBOSE" ]; then
363		if [ ! "$zoneinfo_file" ]; then
364			msg=$( printf "$msg_removing_file" "$_PATH_LOCALTIME" )
365		elif [ "$copymode" ]; then
366			msg=$( printf "$msg_copying_file" \
367			              "$zoneinfo_file" "$_PATH_LOCALTIME" )
368		else
369			msg=$( printf "$msg_creating_symlink" \
370			              "$_PATH_LOCALTIME" "$zoneinfo_file" )
371		fi
372		if [ "$USEDIALOG" ]; then
373			f_dialog_title "$msg_info"
374			f_dialog_msgbox "$msg"
375			f_dialog_title_restore
376		else
377			printf "%s\n" "$msg"
378		fi
379	fi
380
381	[ "$REALLYDOIT" ] || return $SUCCESS
382
383	if [ ! "$zoneinfo_file" ]; then
384		err=$( rm -f "$_PATH_LOCALTIME" 2>&1 )
385		if [ "$err" ]; then
386			if [ "$USEDIALOG" ]; then
387				f_dialog_title "$msg_error"
388				f_dialog_msgbox "$err"
389				f_dialog_title_restore
390			else
391				f_err "%s\n" "$err"
392			fi
393			return $FAILURE
394		fi
395
396		err=$( rm -f "$_PATH_DB" 2>&1 )
397		if [ "$err" ]; then
398			if [ "$USEDIALOG" ]; then
399				f_dialog_title "$msg_error"
400				f_dialog_msgbox "$err"
401				f_dialog_title_restore
402			else
403				f_err "%s\n" "$err"
404			fi
405			return $FAILURE
406		fi
407
408		if [ "$VERBOSE" ]; then
409			msg=$( printf "$msg_removed_file" "$_PATH_LOCALTIME" )
410			if [ "$USEDIALOG" ]; then
411				f_dialog_title "$msg_done"
412				f_dialog_msgbox "$msg"
413				f_dialog_title_restore
414			else
415				printf "%s\n" "$msg"
416			fi
417		fi
418
419		return $SUCCESS
420
421	fi # ! zoneinfo_file
422
423	if [ "$copymode" ]; then
424
425		err=$( rm -f "$_PATH_LOCALTIME" 2>&1 )
426		if [ "$err" ]; then
427			if [ "$USEDIALOG" ]; then
428				f_dialog_title "$msg_error"
429				f_dialog_msgbox "$err"
430				f_dialog_title_restore
431			else
432				f_err "%s\n" "$err"
433			fi
434			return $FAILURE
435		fi
436
437		err=$( umask 222 && : 2>&1 > "$_PATH_LOCALTIME" )
438		if [ "$err" ]; then
439			if [ "$USEDIALOG" ]; then
440				f_dialog_title "$msg_error"
441				f_dialog_msgbox "$err"
442				f_dialog_title_restore
443			else
444				f_err "%s\n" "$err"
445			fi
446			return $FAILURE
447		fi
448
449		err=$( cat "$zoneinfo_file" 2>&1 > "$_PATH_LOCALTIME" )
450		if [ "$err" ]; then
451			if [ "$USEDIALOG" ]; then
452				f_dialog_title "$msg_error"
453				f_dialog_msgbox "$err"
454				f_dialog_title_restore
455			else
456				f_err "%s\n" "$err"
457			fi
458			return $FAILURE
459		fi
460
461	else # ! copymode
462
463		err=$( ( :< "$zoneinfo_file" ) 2>&1 )
464		if [ "$err" ]; then
465			if [ "$USEDIALOG" ]; then
466				f_dialog_title "$msg_error"
467				f_dialog_msgbox "$err"
468				f_dialog_title_restore
469			else
470				f_err "%s\n" "$err"
471			fi
472			return $FAILURE
473		fi
474
475		err=$( rm -f "$_PATH_LOCALTIME" 2>&1 )
476		if [ "$err" ]; then
477			if [ "$USEDIALOG" ]; then
478				f_dialog_title "$msg_error"
479				f_dialog_msgbox "$err"
480				f_dialog_title_restore
481			else
482				f_err "%s\n" "$err"
483			fi
484			return $FAILURE
485		fi
486
487		err=$( ln -s "$zoneinfo_file" "$_PATH_LOCALTIME" 2>&1 )
488		if [ "$err" ]; then
489			if [ "$USEDIALOG" ]; then
490				f_dialog_title "$msg_error"
491				f_dialog_msgbox "$err"
492				f_dialog_title_restore
493			else
494				f_err "%s\n" "$err"
495			fi
496			return $FAILURE
497		fi
498
499	fi # copymode
500
501	if [ "$VERBOSE" ]; then
502		if [ "$copymode" ]; then
503			msg=$( printf "$msg_copied_timezone_file" \
504			              "$zoneinfo_file" "$_PATH_LOCALTIME" )
505		else
506			msg=$( printf "$msg_created_symlink" \
507			              "$_PATH_LOCALTIME" "$zoneinfo_file" )
508		fi
509		if [ "$USEDIALOG" ]; then
510			f_dialog_title "$msg_done"
511			f_dialog_msgbox "$msg"
512			f_dialog_title_restore
513		else
514			printf "%s\n" "$msg"
515		fi
516	fi
517
518	return $SUCCESS
519}
520
521# f_install_zoneinfo $zoneinfo
522#
523# Install a zoneinfo file relative to _PATH_ZONEINFO. The given $zoneinfo
524# will be written to _PATH_DB (usable later with the `-r' flag).
525#
526f_install_zoneinfo()
527{
528	local zoneinfo="$1"
529	local rv
530
531	f_install_zoneinfo_file "$_PATH_ZONEINFO/$zoneinfo"
532	rv=$?
533
534	# Save knowledge for later
535	if [ "$REALLYDOIT" -a $rv -eq $SUCCESS ]; then
536		if true 2> /dev/null > "$_PATH_DB"; then
537			cat <<-EOF > "$_PATH_DB"
538			$zoneinfo
539			EOF
540		fi
541	fi
542
543	return $rv
544}
545
546# f_confirm_zone $filename
547#
548# Prompt the user to confirm the new timezone data. The first (and only)
549# argument should be the pathname to the zoneinfo file, either absolute or
550# relative to `/usr/share/zoneinfo' (e.g., "America/Los_Angeles").
551#
552# The return status is 0 if "Yes" is chosen, 1 if "No", and 255 if Esc is
553# pressed (see dialog(1) for additional details).
554#
555f_confirm_zone()
556{
557	local filename="$1"
558	f_dialog_title "$msg_confirmation"
559	local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
560	f_dialog_title_restore
561	local tm_zone="$( TZ="$filename" date +%Z )"
562	local prompt="$( printf "$msg_look_reasonable" "$tm_zone" )"
563	local height=5 width=72
564
565	if [ "$USE_XDIALOG" ]; then
566		height=$(( $height + 4 ))
567		$DIALOG \
568			--title "$title"       \
569			--backtitle "$btitle"  \
570			--ok-label "$msg_yes" \
571			--cancel-label "$msg_no"   \
572			--yesno "$prompt" $height $width
573	else
574		$DIALOG \
575			--title "$title"       \
576			--backtitle "$btitle"  \
577			--yes-label "$msg_yes" \
578			--no-label "$msg_no"   \
579			--yesno "$prompt" $height $width
580	fi
581}
582
583# f_set_zone_utc
584#
585# Resets to the UTC timezone.
586#
587f_set_zone_utc()
588{
589	f_confirm_zone "" || return $FAILURE
590	f_install_zoneinfo_file ""
591}
592
593############################################################ MAIN
594
595f_dprintf "%s: Successfully loaded." timezone/zones.subr
596
597fi # ! $_TIMEZONE_ZONES_SUBR
598