Home | History | Annotate | Line # | Download | only in tz
tzdata2netbsd revision 1.16
      1 # $NetBSD: tzdata2netbsd,v 1.16 2024/02/05 21:52:38 kre Exp $
      2 
      3 # For use by NetBSD developers when updating to new versions of tzdata.
      4 #
      5 # 0. Be in an up-to-date checkout of src/external/public-domain/tz
      6 #    from NetBSD-current.
      7 # 1. Make sure that you have Paul Eggert's 4K RSA public key in your
      8 #    keyring (62AA7E34, eggert (a] cs.ucla.edu)  It is not required that it be trusted.
      9 # 2. Run this script.  You will be prompted for confirmation before
     10 #    anything major (such as a cvs operation).  The tz versions can be
     11 #    specified as args (new version first, and the previous second) if
     12 #    needed to override the calculated values
     13 # 3. If something fails, abort the script and fix it.
     14 # 4. Re-run this script until you are happy.  It's designed to
     15 #    be re-run over and over, and later runs will try not to
     16 #    redo non-trivial work done by earlier runs.
     17 #
     18 
     19 VERS_PATTERN='2[0-9][0-9][0-9][a-z]'
     20 # This needs to be updated twice every millennium to allow for the
     21 # new millenium's years.
     22 # First in the late xx90's sometime, allow the new one by changing the leading
     23 # digit from a specific value to the class containing the current and
     24 # following values (eg: in 2098 or so, change '2' to be '[23]').
     25 # Then in the following early xx00's sometime, delete the class, and
     26 # leave only the current value as valid (eg: in 3001 or 3002,
     27 # change '[23]' to be just '3'
     28 # Doing it this way helps guard against invalid specifications.
     29 # We could automate this, but it is (IMO) not worth the cost, to avoid a
     30 # twice a millenium edit requirement.
     31 # A more significant (and harder) change will be needed in the late 9990's
     32 # If this script lasts until then, send me a postcard, I'll be waiting for it!
     33 # Things get easier again after that until the late 99990's (etc.)
     34 
     35 # Note the pattern is used on both the old and new version specifiers,
     36 # so it must be able to cope with the shift from one form (eg 2999g)
     37 # to the new one (eg: 3000a) without failing (or the code that uses it
     38 # below needs to be updated).
     39 
     40 # Also note that the option of having a second alpha (1997aa or something)
     41 # to handle years with much activity is handled below, the pattern does not
     42 # need to match those.
     43 # If that convention changes (as of date of writing, it has never been
     44 # exercised) then code changes below will be required.
     45 # Note it doesn't matter (here) if nnnnz is followed by nnnnaa or nnnnza
     46 
     47 DIST_HOST=ftp.iana.org
     48 DIST_PATH=tz
     49 DIST_FILES=releases
     50 
     51 GTZURL=https://github.com/JodaOrg/global-tz/releases/download
     52 
     53 EDITOR=${EDITOR:-vi}
     54 
     55 TZBASE=$(pwd)	|| fail 'Cannot find myself ($PWD)'
     56 cd -P "$TZBASE"	|| fail "Cannot return home: ${TZBASE}"
     57 
     58 WORK_PFX=${TZBASE}/update-work
     59 UPDATE_FROM=${WORK_PFX}/updating.from.version
     60 
     61 usage()
     62 {
     63 	printf >&2 '%s\n'						\
     64 		"Usage: $0 [new-version-id [old-version-id]]"		\
     65 		"     where a version-id is of the form YYYYx (eg: 2018c)" \
     66 		"     or '' for new-version-id (to specify only the old)"  \
     67 		"     and where new-version-id can have =fetch-version-id" \
     68 		"     appended to specify fetching that version instead" \
     69 		"     where the 'fetch-version-id' can be omitted if it" \
     70 		"     is \${new-version-id}gtz  - and simply using '=' means" \
     71 		"     to work out the new-version-id but then use the gtz fork"
     72 
     73 	exit 2
     74 }
     75 
     76 fail()
     77 {
     78 	local IFS=' '
     79 
     80 	printf >&2 '%s\n'	"Error detected:" "   $*" "Aborting."
     81 	exit 1
     82 }
     83 
     84 valid_vers()
     85 {
     86 	case "$2" in
     87 	# The IANA (Eggert) standard version names
     88 	${VERS_PATTERN} | ${VERS_PATTERN}[a-z] )
     89 		;;
     90 	# The alternate (more rational) fork "global timezone" version
     91 	${VERS_PATTERN}gtz | ${VERS_PATTERN}[a-z]gtz )
     92 		;;
     93 	*)	printf >&2 '%s: %s\n' \
     94 		    "Bad form for $1 version specifier '$2'" \
     95 		    "should (usually) be 'YYYYx'"
     96 		return 1
     97 		;;
     98 	esac
     99 	return 0
    100 }
    101 
    102 get_curvers()
    103 {
    104 	local LF=''
    105 	local LIST=iana-listing
    106 	local SED_SCRIPT='
    107 		/tzdata-latest.*-> /{
    108 			s/^.*-> //
    109 			s/\..*$//
    110 			s;^releases/tzdata;;p
    111 			q
    112 		}
    113 		d'
    114 
    115 	test -d "${WORK_PFX}" &&
    116 		test -s "${WORK_PFX}/${LIST}" &&
    117 		test "${WORK_PFX}/${LIST}" -nt dist/CVS &&
    118 		LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) &&
    119 		test -n "${LF}" &&
    120 		NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") &&
    121 		valid_vers new "${NEWVER}"				||
    122 
    123 	ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF &&
    124 					dir ${DIST_PATH} ${WORK_PFX}/${LIST}
    125 					quit
    126 					EOF
    127 		test -s "${WORK_PFX}/${LIST}" &&
    128 		NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") &&
    129 		valid_vers new "${NEWVER}"				||
    130 
    131 	{
    132 		rm -f "${WORK_PFX}/${LIST}"
    133 		fail "Cannot fetch current tzdata version from ${DIST_HOST}"
    134 	}
    135 
    136 	printf '%s\n' "Updating from ${OLDVER} to ${NEWVER}"
    137 }
    138 
    139 argparse()
    140 {
    141 	local OVF OV NV OVy OVs NVy NVs
    142 
    143 	if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print)
    144 	then
    145 		# delete anything old
    146 		test -n "${OVF}" && rm -f "${OVF}"
    147 	fi
    148 
    149 	case "$#" in
    150 	0|1)
    151 		# once we have obtained OLDVER once, never guess it again.
    152 		if  [ -f "${UPDATE_FROM}" ]
    153 		then
    154 			OLDVER=$(cat "${UPDATE_FROM}")
    155 		elif [ -f dist/TZDATA_VERSION ]
    156 		then
    157 			OLDVER=$(cat dist/TZDATA_VERSION)
    158 		elif [ -f dist/version ]
    159 		then
    160 			OLDVER=$(cat dist/version)
    161 		fi
    162 		OLDVER=${OLDVER#tzdata}		# TZDATA_VERS is tzdata-nnnnX
    163 		OLDVER=${OLDVER#-}		# but the '-' is optional
    164 		OLDVERGTZ=${OLDVER}		# This would have been the cvs tag
    165 		OLDVER=${OLDVER%gtz}		# want the base version elsewhere
    166 
    167 		if [ -z "${OLDVER}" ]
    168 		then
    169 			printf >&2 '%s\n'  \
    170 			    'Cannot determine current installed version'  \
    171 			    'Specify it on the command line.'  \
    172 			    ''
    173 			usage
    174 		fi
    175 
    176 		valid_vers old "${OLDVER}" ||
    177 			fail "Calculated bad OLDVER, give as 2nd arg"
    178 		;;
    179 
    180 	2)	valid_vers old "$2" && OLDVER="$2" || usage
    181 		;;
    182 
    183 	*)	usage
    184 		;;
    185 	esac
    186 
    187 	GLOBAL=false
    188 	case "$#:$1" in
    189 	0: | 1: | 2: )
    190 		;;
    191 	1:=|2:=)
    192 		GLOBAL=true;;
    193 	1:=?*|2:=?*)	
    194 		valid_vers fetch "${1#=}" && FETCHVER="${1#=}" || usage
    195 		;;
    196 	1:*=?*|2:*=?*)	
    197 		set -- "{$1%=*}" "${1#*=}"
    198 		valid_vers fetch "$2" && FETCHVER="$2" || usage
    199 		valid_vers new "$1" && NEWVER="$1" || usage
    200 		;;
    201 	1:?*|2:?*)	
    202 		valid_vers new "$1" && NEWVER="$1" || usage
    203 		;;
    204 	*)	usage
    205 		;;
    206 	esac
    207 
    208 	test -z "${NEWVER}" && get_curvers
    209 
    210 	if [ -z "${FETCHVER}" ]
    211 	then
    212 		if $GLOBAL
    213 		then
    214 			FETCHVER=${NEWVER}gtz
    215 		else
    216 			FETCHVER=${NEWVER}
    217 		fi
    218 	fi
    219 
    220 	case "${FETCHVER}" in
    221 	*gtz)	GLOBAL=true;;
    222 	*)	GLOBAL=false;;
    223 	esac
    224 
    225 	if [ "${NEWVER}" = "${OLDVER}" ]
    226 	then
    227 		printf 'New and old versions both %s; nothing to do\n' \
    228 		     "${NEWVER}"
    229 		exit 0
    230 	fi
    231 
    232 	printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" ||
    233 	    fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}"
    234 
    235 	# Do version upgrade test using base version names, ignoring
    236 	# the "gtz" in the "global timezone" versions, so we can
    237 	# switch back and forth between use of those as circumstances change
    238 	OV=${OLDVER%gtz}
    239 	NV=${NEWVER%gtz}
    240 
    241 	OVy=${OV%%[!0-9]*}
    242 	OVs=${OV#${OVy}}
    243 	NVy=${NV%%[!0-9]*}
    244 	NVs=${NV#${NVy}}
    245 
    246 	# To get all the permutations correct, we need to separate
    247 	# the year and suffix parts of the version IDs (done just above)
    248 	# and then compare them separately.  The suffix is only relevant
    249 	# to the result when the years are the same.
    250 
    251 	# We compare the length of the suffix separately to the suffix
    252 	# itself, a multi-char suffix has never happened (and is never
    253 	# likely to) - but in the event that prediction is wrong, we don't
    254 	# know (yet) what is to come after 'z' - it might be 'za' 'zb'
    255 	# ... to 'zz" then 'zza' ... or it might be 'aa' 'ab' ... 'az' 'ba'...
    256 	# we need to handle both possibilities.  Two things stand out from
    257 	# those: 1. a longer suffix is always going to be for a newer version
    258 	# than a shorter one;  2. equal length suffixes can be compared as
    259 	# strings
    260 
    261 	if [ "${OVy}" -gt "${NVy}" ]			|| {
    262 		[ "${OVy}" -eq "${NVy}" ]	&& {
    263 			[ "${#OVs}" -gt "${#NVs}" ]		||
    264 			LC_COLLATE=C [ "${OVs}" '>' "${NVs}" ]
    265 		}
    266         } then
    267 		local reply
    268 
    269 		printf '%s\n' "Update would revert ${OLDVER} to ${NEWVER}"
    270 		read -p "Is reversion intended? " reply
    271 		case "${reply}" in
    272 		[Yy]*)	;;
    273 		*)	printf '%s\n' OK. Aborted.
    274 			rm -f "${UPDATE_FROM}"
    275 			exit 1
    276 			;;
    277 		esac
    278 	fi
    279 
    280 	return 0
    281 }
    282 
    283 setup_versions()
    284 {
    285 	# Uppercase variants of OLDVER and NEWVER
    286 	OLDVER_UC="$( echo "${OLDVERGTZ}" | tr '[a-z]' '[A-Z]' )"
    287 	NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )"
    288 
    289 	# Tags for use with version control systems
    290 	CVSOLDTAG="TZDATA${OLDVER_UC}"
    291 	CVSNEWTAG="TZDATA${NEWVER_UC}"
    292 	CVSBRANCHTAG="TZDATA"
    293 	GITHUBTAG="${NEWVER}"
    294 
    295 	if $GLOBAL && [ "${CVSNEWTAG%GTZ}" = "${CVSNEWTAG}" ]
    296 	then
    297 		CVSNEWTAG=${CVSNEWTAG}GTZ
    298 	fi
    299 
    300 	# URLs for fetching distribution files, etc.
    301 	if $GLOBAL
    302 	then
    303 		DISTURL=${GTZURL}/${FETCHVER}/tzdata${FETCHVER}.tar.gz
    304 		unset SIGURL
    305 	else
    306 		DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}"
    307 		DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz"
    308 		SIGURL="${DISTURL}.asc"
    309 	fi
    310 	NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS"
    311 
    312 	# Directories
    313 	REPODIR="src/external/public-domain/tz/dist"
    314 				# relative to the NetBSD CVS repo
    315 	TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist
    316 	WORKDIR="${WORK_PFX}/${NEWVER}"
    317 	EXTRACTDIR="${WORKDIR}/extract"
    318 
    319 	# Files in the work directory
    320 	DISTFILE="${WORKDIR}/${DISTURL##*/}"
    321 	SIGFILE="${DISTFILE}.asc"
    322 	PGPVERIFYLOG="${WORKDIR}/pgpverify.log"
    323 	NEWSFILE="${WORKDIR}/NEWS"
    324 	NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed"
    325 	IMPORTMSGFILE="${WORKDIR}/import.msg"
    326 	IMPORTDONEFILE="${WORKDIR}/import.done"
    327 	MERGSMSGFILE="${WORKDIR}/merge.msg"
    328 	MERGEDONEFILE="${WORKDIR}/merge.done"
    329 	COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done"
    330 
    331 	printf '%s\n' "${CVSOLDTAG}"  > "${WORK_PFX}/updating_from"
    332 }
    333 
    334 DOIT()
    335 {
    336 	local really_do_it=false
    337 	local reply
    338 
    339 	echo "In directory $(pwd)"
    340 	echo "ABOUT TO DO:" "$(shell_quote "$@")"
    341 	read -p "Really do it? [yes/no/quit] " reply
    342 	case "${reply}" in
    343 	[yY]*)	really_do_it=true ;;
    344 	[nN]*)	really_do_it=false ;;
    345 	[qQ]*)
    346 		echo "Aborting"
    347 		return 1
    348 		;;
    349 	*)	echo "Huh?"
    350 		return 1
    351 		;;
    352 	esac
    353 	if $really_do_it; then
    354 		echo "REALLY DOING IT NOW..."
    355 		"$@"
    356 	else
    357 		echo "NOT REALLY DOING THE ABOVE COMMAND"
    358 	fi
    359 }
    360 
    361 # Quote args to make them safe in the shell.
    362 # Usage: quotedlist="$(shell_quote args...)"
    363 #
    364 # After building up a quoted list, use it by evaling it inside
    365 # double quotes, like this:
    366 #    eval "set -- $quotedlist"
    367 # or like this:
    368 #    eval "\$command $quotedlist \$filename"
    369 #
    370 shell_quote()
    371 (
    372 	local result=''
    373 	local arg qarg
    374 	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
    375 	for arg in "$@" ; do
    376 		case "${arg}" in
    377 		'')
    378 			qarg="''"
    379 			;;
    380 		*[!-./a-zA-Z0-9]*)
    381 			# Convert each embedded ' to '\'',
    382 			# then insert ' at the beginning of the first line,
    383 			# and append ' at the end of the last line.
    384 			# Finally, elide unnecessary '' pairs at the
    385 			# beginning and end of the result and as part of
    386 			# '\'''\'' sequences that result from multiple
    387 			# adjacent quotes in the input.
    388 			qarg="$(printf '%s\n' "$arg" | \
    389 			    ${SED:-sed} -e "s/'/'\\\\''/g" \
    390 				-e "1s/^/'/" -e "\$s/\$/'/" \
    391 				-e "1s/^''//" -e "\$s/''\$//" \
    392 				-e "s/'''/'/g"
    393 				)"
    394 			;;
    395 		*)
    396 			# Arg is not the empty string, and does not contain
    397 			# any unsafe characters.  Leave it unchanged for
    398 			# readability.
    399 			qarg="${arg}"
    400 			;;
    401 		esac
    402 		result="${result}${result:+ }${qarg}"
    403 	done
    404 	printf '%s\n' "$result"
    405 )
    406 
    407 validate_pwd()
    408 {
    409 	local P="$(pwd)" || return 1
    410 
    411 	test -d "${P}" &&
    412 	    test -d "${P}/CVS" &&
    413 	    test -d "${P}/dist" &&
    414 	    test -f "${P}/dist/zone.tab" &&
    415 	    test -f "${P}/tzdata2netbsd" || {
    416 		printf >&2 '%s\n' "Please change to the correct directory"
    417 		return 1
    418 	}
    419 	
    420 	test -f "${P}/CVS/Tag" && {
    421 
    422 		# Here (for local use only) if needed for private branch work
    423 		# insert tests for the value of $(cat "${P}/CVS/Tag") and
    424 		# allow your private branch tag to pass. Eg:
    425 
    426 		#	case "$(cat "${P}/CVS/Tag")" in
    427 		#	my-branch-name)	return 0;;
    428 		#	esac
    429 
    430 		# Do not commit a version of this script modified that way,
    431 		# (not even on the private branch) - keep it as a local
    432 		# modified file.  (This script will not commit it.)
    433 
    434 		printf >&2 '%s\n' \
    435 		    "This script should be run in a checkout of HEAD only"
    436 		return 1
    437 	}
    438 
    439 	return 0
    440 }
    441 
    442 findcvsroot()
    443 {
    444 	[ -n "${CVSROOT}" ] && return 0
    445 	CVSROOT="$( cat ./CVS/Root )"
    446 	[ -n "${CVSROOT}" ] && return 0
    447 	echo >&2 "Failed to set CVSROOT value"
    448 	return 1
    449 }
    450 
    451 mkworkpfx()
    452 {
    453 	mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}"
    454 }
    455 mkworkdir()
    456 {
    457 	mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}"
    458 }
    459 
    460 cvsupdate()
    461 (
    462 	# Make sure our working directory is up to date (and HEAD)
    463 	cd "${TZBASE}/dist"			|| exit 1
    464 	cvs -d "${CVSROOT}" -q update -AdP	|| exit 2
    465 )
    466 
    467 fetch()
    468 {
    469 	[ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" ||
    470 		fail "fetch of ${DISTFILE} failed"
    471 
    472 	if [ -n "${SIGURL}" ]
    473 	then
    474 		[ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" ||
    475 			fail "fetch of ${SIGFILE} failed"
    476 	fi
    477 
    478 	[ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" ||
    479 		fail "fetch of ${NEWSFILE} failed"
    480 }
    481 
    482 checksig()
    483 {
    484 	{ gpg --verify "${SIGFILE}" "${DISTFILE}"
    485 	  echo gpg exit status $?
    486 	} 2>&1 | tee "${PGPVERIFYLOG}"
    487 
    488 	# The output should contain lines that match all the following regexps
    489 	#
    490 	while read line; do
    491 		if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"; then
    492 			echo >&2 "Failed to verify signature: ${line}"
    493 			return 1
    494 		fi
    495 	done <<'EOF'
    496 gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C)
    497 gpg: Good signature from "Paul Eggert <eggert (a] cs.ucla.edu>"
    498 gpg exit status 0
    499 EOF
    500 }
    501 
    502 extract()
    503 {
    504 	[ -f "${EXTRACTDIR}/zone.tab" ] && return
    505 	mkdir -p "${EXTRACTDIR}"
    506 	tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}"
    507 }
    508 
    509 rflist()
    510 (
    511 	 test "${1}" && cd "${1}" && find * -print | sort
    512 )
    513 
    514 zonelists()
    515 {
    516 	[ -f "${WORKDIR}"/addedzones ] && return
    517 
    518 	rm -fr "${WORK_PFX}"/oldzones "${WORK_PFX}"/newzones
    519 
    520 	(
    521 		cd "${TZBASE}/share/zoneinfo" || exit 1
    522 
    523 		make	TOOL_ZIC=/usr/sbin/zic			\
    524 			DESTDIR=				\
    525 			TZBUILDDIR="${WORK_PFX}"/oldzones	\
    526 			TZDIR="${WORK_PFX}"/oldzones		\
    527 			TZDISTDIR="${TZBASE}"/dist		\
    528 				posix_only >/dev/null 2>&1
    529 
    530 	) || fail 'Unable to compile old zone data files'
    531 
    532 	(
    533 		cd "${TZBASE}/share/zoneinfo" || exit 1
    534 
    535 		make	TOOL_ZIC=/usr/sbin/zic			\
    536 			DESTDIR=				\
    537 			TZBUILDDIR="${WORK_PFX}"/newzones	\
    538 			TZDIR="${WORK_PFX}"/newzones		\
    539 			TZDISTDIR="${EXTRACTDIR}"		\
    540 				posix_only >/dev/null 2>&1
    541 
    542 	) || fail 'Unable to compile new zone data files'
    543 
    544 	rflist "${WORK_PFX}"/oldzones > "${WORKDIR}"/oldzones
    545 	rflist "${WORK_PFX}"/newzones > "${WORKDIR}"/newzones
    546 
    547 	if cmp -s "${WORKDIR}"/oldzones "${WORKDIR}"/newzones >/dev/null
    548 	then
    549 		printf "No zones added or deleted by this update"
    550 		> "${WORKDIR}"/removedzones
    551 		> "${WORKDIR}"/addedzones
    552 		return 0
    553 	fi
    554 
    555 	comm -23 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
    556 		> "${WORKDIR}"/removedzones
    557 
    558 	test "${REMOVEOK:-no}" != yes && test -s "${WORKDIR}"/removedzones && {
    559 		printf '%s\n' 'This update wants to remove these zone files:' ''
    560 		sed 's/^/	/' < "${WORKDIR}"/removedzones
    561 		printf '%s\n' '' 'It probably should not' ''
    562 
    563 		printf 'If this is OK, rerun this script with REMOVEOK=yes\n'
    564 		printf 'Otherwise, fix the problem, and then rerun the script\n'
    565 		exit 1
    566 	}
    567 
    568 	comm -13 "${WORKDIR}"/oldzones "${WORKDIR}"/newzones \
    569 		> "${WORKDIR}"/addedzones
    570 
    571 	test -s "${WORKDIR}"/addedzones && {
    572 		printf '%s\n' '' '********************************* NOTE:' \
    573 			'********************************* New Zones Created' \
    574 			''
    575 		sed 's/^/	/' < "${WORKDIR}"/addedzones
    576 		printf '%s\n' '' '*********************************' ''
    577 	}
    578 
    579 	return 0
    580 }
    581 
    582 updatedzones()
    583 {
    584 	[ -f "${WORKDIR}"/.zonesOK ] && return
    585 
    586 	rm -fr "${WORK_PFX}"/updzones
    587 
    588 	(
    589 		cd "${TZBASE}/share/zoneinfo" || exit 1
    590 
    591 		make	TOOL_ZIC=/usr/sbin/zic			\
    592 			DESTDIR=				\
    593 			TZBUILDDIR="${WORK_PFX}"/updzones	\
    594 			TZDIR="${WORK_PFX}"/updzones		\
    595 			TZDISTDIR="${TZBASE}"/dist		\
    596 				posix_only >/dev/null 2>&1
    597 
    598 	) || fail 'Unable to compile updated zone data.   HELP'
    599 
    600 	rflist "${WORK_PFX}"/updzones > "${WORKDIR}"/updzones
    601 
    602 	cmp -s "${WORKDIR}"/newzones "${WORKDIR}"/updzones || {
    603 
    604 		printf '%s\n' '' '*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*' \
    605 			'After cvs work, zones created are not as intended' \
    606 			'-------------------------------------------------' \
    607 			'Zones not created but should have been:'
    608 		comm -23 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
    609 			sed 's/^/	/'
    610 		printf '%s\n' \
    611 			'-------------------------------------------------' \
    612 			'Zones created that should not have been:'
    613 		comm -13 "${WORKDIR}"/newzones "${WORKDIR}"/updzones |
    614 			sed 's/^/	/'
    615 		prinrf '%s\n' \
    616 			'-------------------------------------------------'
    617 
    618 		fail 'cvs import/merge/update/commit botch'
    619 	}
    620 
    621 	> "${WORKDIR}"/.zonesOK
    622 }
    623 
    624 addnews()
    625 {
    626 	[ -f "${EXTRACTDIR}/NEWS" ] && return
    627 	cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS
    628 }
    629 
    630 # Find the relevant part of the NEWS file for all releases between
    631 # OLDVER and NEWVER, and save them to NEWSTRIMFILE.
    632 #
    633 trimnews()
    634 {
    635 	[ -s "${NEWSTRIMFILE}" ] && return
    636 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    637 	    '
    638 		BEGIN {inrange = 0}
    639 		/^Release [0-9]+[a-z]+ - .*/ {
    640 			# "Release <version> - <date>"
    641 			# Note: must handle transition from 2018z to 2018aa
    642 			# Assumptions: OLDVER and NEWVER have been sanitized,
    643 			# and format of NEWS file does not alter (and
    644 			# contains valid data)
    645 			inrange = ((length($2) > length(oldver) || \
    646 					$2 > oldver) && \
    647 				(length($2) < newver || $2 <= newver))
    648 		}
    649 		// { if (inrange) print; }
    650 	    ' \
    651 		<"${NEWSFILE}" >"${NEWSTRIMFILE}"
    652 
    653 	if $GLOBAL
    654 	then
    655 		printf '%s\n' "tzdata-${NEWVER}gtz"
    656 	else
    657 		printf '%s\n' "tzdata-${NEWVER}"
    658 	fi > ${TZDISTDIR}/TZDATA_VERSION
    659 }
    660 
    661 # Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections,
    662 # keeping only the first sentence from paragraphs in other sections,
    663 # and changing the format.
    664 #
    665 # The result should be edited by hand before performing a cvs commit.
    666 # A message to that effect is inserted at the beginning of the file.
    667 #
    668 mkimportmsg()
    669 {
    670 	[ -s "${IMPORTMSGFILE}" ] && return
    671 	{ cat <<EOF
    672 EDIT ME: Edit this file and then delete the lines marked "EDIT ME".
    673 EDIT ME: This file will be used as a log message for the "cvs commit" that
    674 EDIT ME: imports tzdata${NEWVER}.  The initial contents of this file were
    675 EDIT ME: generated from ${NEWSFILE}.
    676 EDIT ME: 
    677 EOF
    678 	awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \
    679 	    -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \
    680 	    '
    681 		BEGIN {
    682 			bullet = "  * ";
    683 			indent = "    ";
    684 			blankline = 0;
    685 			goodsection = 0;
    686 			havesentence = 0;
    687 			print "Import tzdata"newver" from "disturl;
    688 			#print "and NEWS file from "newsurl;
    689 		}
    690 		/^Release/ {
    691 			# "Release <version> - <date>"
    692 			ver = $2;
    693 			date = gensub(".* - ", "", 1, $0);
    694 			print "";
    695 			print "Summary of changes in tzdata"ver \
    696 				" ("date"):";
    697 		}
    698 		/^$/ { blankline = 1; havesentence = 0; }
    699 		/^  Changes / { goodsection = 0; }
    700 		/^  Changes to future timestamps/ { goodsection = 1; }
    701 		/^  Changes to past timestamps/ { goodsection = 1; }
    702 		/^  Changes to documentation/ || \
    703 		/^  Changes to commentary/ {
    704 			t = gensub("^ *", "", 1, $0);
    705 			t = gensub("\\.*$", ".", 1, t);
    706 			print bullet t;
    707 			goodsection = 0;
    708 		}
    709 		/^    .*/ && goodsection {
    710 			# In a paragraph in a "good" section.
    711 			# Ignore leading spaces, and ignore anything
    712 			# after the first sentence.
    713 			# First line of paragraph gets a bullet.
    714 			t = gensub("^ *", "", 1, $0);
    715 			t = gensub("\\. .*", ".", 1, t);
    716 			if (blankline) print bullet t;
    717 			else if (! havesentence) print indent t;
    718 			havesentence = (havesentence || (t ~ "\\.$"));
    719 		}
    720 		/./ { blankline = 0; }
    721 	    ' \
    722 		<"${NEWSTRIMFILE}"
    723 	} >"${IMPORTMSGFILE}"
    724 
    725 	if [ -s "${WORKDIR}"/addedzones ]
    726 	then
    727 		printf '%s\n' '' 'Zones added by this update:'
    728 		sed 's/^/	/' < "${WORKDIR}"/addedzones
    729 	fi >> "${IMPORTMSGFILE}"
    730 
    731 	if [ -s "${WORKDIR}"/removedzones ]
    732 	then
    733 		printf '%s\n' '' 'Zones removed by this update:'
    734 		sed 's/^/	/' < "${WORKDIR}"/removedzones
    735 	fi >> "${IMPORTMSGFILE}"
    736 
    737 }
    738 
    739 editimportmsg()
    740 {
    741 	if [ -s "${IMPORTMSGFILE}" ] && ! grep -q '^EDIT' "${IMPORTMSGFILE}"
    742 	then
    743 		return 0 # file has already been edited
    744 	fi
    745 	# Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the
    746 	# user can easily consult NEWSFILE while editing IMPORTMSGFILE.
    747 	${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}"
    748 }
    749 
    750 cvsimport()
    751 {
    752 	if [ -e "${IMPORTDONEFILE}" ]; then
    753 		cat >&2 <<EOF
    754 The CVS import has already been performed.
    755 EOF
    756 		return 0
    757 	fi
    758 	if ! [ -s "${IMPORTMSGFILE}" ] || grep -q '^EDIT' "${IMPORTMSGFILE}"
    759 	then
    760 		cat >&2 <<EOF
    761 The message file ${IMPORTMSGFILE}
    762 has not been properly edited.
    763 Not performing cvs import.
    764 EOF
    765 		return 1
    766 	fi
    767 	( cd "${EXTRACTDIR}" &&
    768 	  DOIT cvs -d "${CVSROOT}" import -I ! -m "$(cat "${IMPORTMSGFILE}")" \
    769 		"${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}"
    770 	) && touch "${IMPORTDONEFILE}"
    771 }
    772 
    773 cvsmerge()
    774 {
    775 
    776 	cd "${TZDISTDIR}" || exit 1
    777 	if [ -e "${MERGEDONEFILE}" ]
    778 	then
    779 		cat >&2 <<EOF
    780 The CVS merge has already been performed.
    781 EOF
    782 		return 0
    783 	fi
    784 	DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" &&
    785 		touch "${MERGEDONEFILE}"
    786 	printf '%s\n' ================================== \
    787 		'The following differences exist between the merge results' \
    788 		'and the imported files:' '================================='
    789 	diff -ur "${EXTRACTDIR}" .
    790 	printf '%s\n' ==================================
    791 }
    792 
    793 resolveconflicts()
    794 {
    795 	cd "${TZDISTDIR}" || exit 1
    796 	if grep -l '^[<=>][<=>][<=>]' *
    797 	then
    798 		cat <<EOF
    799 There appear to be conflicts in the files listed above.
    800 Resolve conflicts, then re-run this script.
    801 EOF
    802 		return 1
    803 	fi
    804 }
    805 
    806 cvscommitmerge()
    807 {
    808 	cd "${TZDISTDIR}" || exit 1
    809 	if grep -l '^[<=>][<=>][<=>]' *
    810 	then
    811 		cat >&2 <<EOF
    812 There still appear to be conflicts in the files listed above.
    813 Not performing cvs commit.
    814 EOF
    815 		return 1
    816 	fi
    817 	if [ -e "${COMMITMERGEDONEFILE}" ]; then
    818 		cat >&2 <<EOF
    819 The CVS commmit (of the merge result) has already been performed.
    820 EOF
    821 		return 0
    822 	fi
    823 	DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" &&
    824 		touch "${COMMITMERGEDONEFILE}"
    825 }
    826 
    827 setlistupdate()
    828 {
    829 	if [ -s "${WORKDIR}"/addedzones ] ||
    830 	   [ -s "${WORKDIR}"/removedzones ]
    831 	then	(
    832 			# Do all the preparatory work first, so
    833 			# when we get to manipulating the sets list file
    834 			# it all happens quickly...
    835 
    836 			while read file
    837 			do
    838 				printf '\\!zoneinfo/%s!{ %s ; %s ; }\n'     \
    839 					"${file}"			    \
    840 					's/sys-share	/obsolete	/'  \
    841 					's/	share$/	obsolete/'
    842 			done < "${WORKDIR}"/removedzones > "${WORKDIR}/sedcmds"
    843 
    844 			while read file
    845 			do
    846 				P=./usr/share/zoneinfo/"${file}"
    847 				T2='		'
    848 				case "$(( 48 - ${#P} ))" in
    849 				-*|0)	T2='	'	T='	'	 ;;
    850 				[12345678])		T='	'	 ;;
    851 				9|1[0123456])		T='		';;
    852 				1[789]|2[01234]) T='			';;
    853 				2[5-9]|3[012])
    854 					T='				';;
    855 				# the following cases can't happen,
    856 				# but for completeness...
    857 				3[3-9])
    858 				    T='					';;
    859 				*)  T='				'
    860 				    T="${T}		"		;;
    861 				esac
    862 
    863 				if [ -d "${WORKDIR}/newzones/${file}" ]
    864 				then
    865 					printf '%s%sbase-sys-share\n' \
    866 						"${P}" "${T}"
    867 					continue
    868 				fi
    869 
    870 				printf '%s%sbase-sys-share%sshare\n' \
    871 					"${P}" "${T}" "${T2}"
    872 
    873 				# Deal with possibility that a new file
    874 				# might have previously existed, and then
    875 				# been deleted - marked obsolete
    876 				printf '\\!^%s	.*obsolete!d\n' "${P}" \
    877 					>> "${WORKDIR}/sedcmds"
    878 
    879 			done < "${WORKDIR}"/addedzones > "${WORKDIR}/setadded"
    880 
    881 			printf '$r %s\n' "${WORKDIR}/setadded" \
    882 				>> "${WORKDIR}/sedcmds"
    883 
    884 			if ! [ -s "${WORKDIR}/sedcmds" ]	# impossible?
    885 			then
    886 				exit 0
    887 			fi
    888 
    889 			MSG=$( 
    890 				printf 'tzdata update to %s\n' "$NEWVER"
    891 				if [ -s "${WORKDIR}"/addedzones ]
    892 				then
    893 					printf 'Added zoneinfo files:\n'
    894 					sed 's/^/	/'	\
    895 						< "${WORKDIR}"/addedzones
    896 				fi
    897 				if [ -s "${WORKDIR}"/removedzones ]
    898 				then
    899 					printf  'Removed zoneinfo files:\n'
    900 					sed 's/^/	/'	\
    901 						< "${WORKDIR}"/removedzones
    902 				fi
    903 				printf '\nX'
    904 			)
    905 			MSG=${MSG%X}
    906 
    907 			# Now is where the changes start happening...
    908 
    909 			cd ${TZBASE}/../../../distrib/sets || exit 1
    910 			cd lists/base || exit 2
    911 			DOIT cvs -d "${CVSROOT}" -q update mi || exit 3
    912 			cp mi mi.unsorted || exit 4
    913 			sh ../../sort-list mi || exit 5
    914 			cmp -s mi mi.unsorted ||
    915 				DOIT cvs -d "${CVSROOT}" -q commit \
    916 					-m 'Sort (NFCI)' mi || exit 6
    917 			rm -f mi.unsorted
    918 			sed -f "${WORKDIR}/sedcmds" < mi > mi.new || exit 7
    919 			test ! -s mi.new || cmp -s mi mi.new && {
    920 				printf 'Failed to make changes to set lists'
    921 				exit 8
    922 			}
    923 			mv mi.new mi || exit 9
    924 			sh ../../sort-list mi || exit 10
    925 			DOIT cvs -d "${CVSROOT}" commit -m "${MSG}" mi ||
    926 				exit 11
    927 			printf 'Sets list successfully updated'
    928 			exit 0
    929 		) || {
    930 			printf '%s: %d\n%s %s\n' 'Sets list update failed' "$?"\
    931 				'Update the sets list' \
    932 				'(src/distrib/sets/lists/base/mi) manually'
    933 			if [ -s "${WORKDIR}"/removedzones ]
    934 			then
    935 				printf 'Removed Zones:'
    936 				sed 's/^/	/' < "${WORKDIR}"/removedzones
    937 			fi
    938 			if [ -s "${WORKDIR}"/addedzones ]
    939 			then
    940 				printf 'Added Zones:'
    941 				sed 's/^/	/' < "${WORKDIR}"/addedzones
    942 			fi
    943 		}
    944 	fi
    945 }
    946 
    947 extra()
    948 {
    949 	cat <<EOF
    950 Also do the following:
    951  * Edit and commit  src/doc/3RDPARTY
    952  * Edit and commit  src/doc/CHANGES
    953  * Edit and commit  src/distrib/sets/lists/base/mi
    954  *      if the set of installed files altered (if not done by the script).
    955  * Submit pullup requests for all active release branches.
    956  * rm -rf ${WORK_PFX}  (optional)
    957  * Verify that
    958   ${UPDATE_FROM}
    959  * no longer exists.
    960 EOF
    961 }
    962 
    963 main()
    964 {
    965 	set -e
    966 	validate_pwd
    967 	findcvsroot
    968 	mkworkpfx
    969 
    970 	argparse "$@"
    971 
    972 	cvsupdate || fail 'working directory (dist) cvs update failed:'" $?"
    973 
    974 	setup_versions
    975 	mkworkdir
    976 	fetch
    977 	$GLOBAL || checksig
    978 	extract
    979 
    980 	zonelists
    981 
    982 	addnews
    983 	trimnews
    984 	mkimportmsg
    985 	editimportmsg
    986 	cvsimport
    987 	cvsmerge
    988 	resolveconflicts
    989 	cvscommitmerge
    990 	updatedzones
    991 
    992 	setlistupdate
    993 
    994 	rm -f "${UPDATE_FROM}"
    995 	rm -fr	"${WORK_PFX}"/oldzones \
    996 		"${WORK_PFX}"/newzones \
    997 		"${WORK_PFX}"/updzones
    998 	extra
    999 }
   1000 
   1001 main "$@"
   1002