1# $NetBSD: tzdata2netbsd,v 1.12 2018/03/24 01:54:48 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@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 19VERS_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# Tnen in the following early xx00's sometime, delete the class, and insert 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 46DIST_HOST=ftp.iana.org 47DIST_PATH=tz 48DIST_FILES=releases 49 50EDITOR=${EDITOR:-vi} 51WORK_PFX=$(pwd)/update-work || fail "Cannot obtain PWD" 52UPDATE_FROM=${WORK_PFX}/updating.from.version 53 54usage() 55{ 56 printf >&2 '%s\n' \ 57 "Usage: $0 [new-version-id [old-version-id]]" \ 58 " where a version-id is of the form YYYYx (eg: 2018c)" \ 59 " or '' for new-version-id (to specify only the old)" 60 exit 2 61} 62 63fail() 64{ 65 local IFS=' ' 66 67 printf >&2 '%s\n' "Error detected:" " $*" "Aborting." 68 exit 1 69} 70 71valid_vers() 72{ 73 case "$2" in 74 ${VERS_PATTERN} | ${VERS_PATTERN}[a-z] ) 75 ;; 76 *) printf >&2 '%s: %s\n' \ 77 "Bad form for $1 version specifier '$2'" \ 78 "should (usually) be 'YYYYx'" 79 return 1 80 ;; 81 esac 82 return 0 83} 84 85get_curvers() 86{ 87 local LF='' 88 local LIST=iana-listing 89 local SED_SCRIPT=' 90 /tzdata-latest.*-> /{ 91 s/^.*-> // 92 s/\..*$// 93 s;^releases/tzdata;;p 94 q 95 } 96 d' 97 98 test -d "${WORK_PFX}" && 99 test -s "${WORK_PFX}/${LIST}" && 100 test "${WORK_PFX}/${LIST}" -nt dist/CVS && 101 LF=$(find "${WORK_PFX}" -name "${LIST}" -mtime -1 -print) && 102 test -n "${LF}" && 103 NEWVER=$(sed -n < "${LF}" "${SED_SCRIPT}") && 104 valid_vers new "${NEWVER}" || 105 106 ftp >/dev/null 2>&1 -ia "${DIST_HOST}" <<- EOF && 107 dir ${DIST_PATH} ${WORK_PFX}/${LIST} 108 quit 109 EOF 110 test -s "${WORK_PFX}/${LIST}" && 111 NEWVER=$(sed -n < "${WORK_PFX}/${LIST}" "${SED_SCRIPT}") && 112 valid_vers new "${NEWVER}" || 113 114 { 115 rm -f "${WORK_PFX}/${LIST}" 116 fail "Cannot fetch current tzdata version from ${DIST_HOST}" 117 } 118 119 printf '%s\n' "Updating from ${OLDVER} to ${NEWVER}" 120} 121 122argparse() 123{ 124 local OVF 125 126 if OVF=$(find "${WORK_PFX}" -name "${UPDATE_FROM##*/}" -mtime +2 -print) 127 then 128 # delete anything old 129 test -n "${OVF}" && rm -f "${OVF}" 130 fi 131 132 case "$#" in 133 0|1) 134 # once we have obtained OLDVER once, never guess it again. 135 test -f "${UPDATE_FROM}" && OLDVER=$(cat "${UPDATE_FROM}") || 136 137 OLDVER=$(cat dist/version) || { 138 printf >&2 '%s\n' \ 139 "Cannot determine current installed version" \ 140 "Specify it on the command line." \ 141 "" 142 usage 143 } 144 145 valid_vers old "${OLDVER}" || 146 fail "Calculated bad OLDVER, give as 2nd arg" 147 ;; 148 149 2) valid_vers old "$2" && OLDVER="$2" || usage 150 ;; 151 152 *) usage 153 ;; 154 esac 155 156 case "$#:$1" in 157 0: | 1: | 2: ) 158 ;; 159 1:?*|2:?*) 160 valid_vers new "$1" && NEWVER="$1" || usage 161 ;; 162 *) usage 163 ;; 164 esac 165 166 test -z "${NEWVER}" && get_curvers 167 168 test "${NEWVER}" = "${OLDVER}" && { 169 printf '%s\n' \ 170 "New and old versions both ${NEWVER}: nothing to do" 171 exit 0 172 173 } 174 175 printf '%s\n' "${OLDVER}" > "${UPDATE_FROM}" || 176 fail "Unable to preserve old version ${OLDVER} in ${UPDATE_FROM}" 177 178 test "${#NEWVER}" -gt "${#OLDVER}" || 179 test "${NEWVER}" '>' "${OLDVER}" || 180 { 181 local reply 182 183 printf '%s\n' \ 184 "Update would revert ${OLDVER} to ${NEWVER}" 185 read -p "Is reversion intended? " reply 186 case "${reply}" in 187 [Yy]*) ;; 188 *) printf '%s\n' OK. Aborted. 189 rm -f "${UPDATE_FROM}" 190 exit 1 191 ;; 192 esac 193 } 194 195 return 0 196} 197 198setup_versions() 199{ 200 # Uppercase variants of OLDVER and NEWVER 201 OLDVER_UC="$( echo "${OLDVER}" | tr '[a-z]' '[A-Z]' )" 202 NEWVER_UC="$( echo "${NEWVER}" | tr '[a-z]' '[A-Z]' )" 203 204 # Tags for use with version control systems 205 CVSOLDTAG="TZDATA${OLDVER_UC}" 206 CVSNEWTAG="TZDATA${NEWVER_UC}" 207 CVSBRANCHTAG="TZDATA" 208 GITHUBTAG="${NEWVER}" 209 210 # URLs for fetching distribution files, etc. 211 DISTURL="ftp://${DIST_HOST}/${DIST_PATH}/${DIST_FILES}" 212 DISTURL="${DISTURL}/tzdata${NEWVER}.tar.gz" 213 SIGURL="${DISTURL}.asc" 214 NEWSURL="https://github.com/eggert/tz/raw/${GITHUBTAG}/NEWS" 215 216 # Directories 217 REPODIR="src/external/public-domain/tz/dist" 218 # relative to the NetBSD CVS repo 219 TZDISTDIR="$(pwd)/dist" # should be .../external/public-domain/tz/dist 220 WORKDIR="${WORK_PFX}/${NEWVER}" 221 EXTRACTDIR="${WORKDIR}/extract" 222 223 # Files in the work directory 224 DISTFILE="${WORKDIR}/${DISTURL##*/}" 225 SIGFILE="${DISTFILE}.asc" 226 PGPVERIFYLOG="${WORKDIR}/pgpverify.log" 227 NEWSFILE="${WORKDIR}/NEWS" 228 NEWSTRIMFILE="${WORKDIR}/NEWS.trimmed" 229 IMPORTMSGFILE="${WORKDIR}/import.msg" 230 IMPORTDONEFILE="${WORKDIR}/import.done" 231 MERGSMSGFILE="${WORKDIR}/merge.msg" 232 MERGEDONEFILE="${WORKDIR}/merge.done" 233 COMMITMERGEDONEFILE="${WORKDIR}/commitmerge.done" 234 235 printf '%s\n' "${CVSOLDTAG}" > "${WORK_PFX}/updating_from" 236} 237 238DOIT() 239{ 240 local really_do_it=false 241 local reply 242 243 echo "In directory $(pwd)" 244 echo "ABOUT TO DO:" "$(shell_quote "$@")" 245 read -p "Really do it? [yes/no/quit] " reply 246 case "${reply}" in 247 [yY]*) really_do_it=true ;; 248 [nN]*) really_do_it=false ;; 249 [qQ]*) 250 echo "Aborting" 251 return 1 252 ;; 253 *) echo "Huh?" 254 return 1 255 ;; 256 esac 257 if $really_do_it; then 258 echo "REALLY DOING IT NOW..." 259 "$@" 260 else 261 echo "NOT REALLY DOING THE ABOVE COMMAND" 262 fi 263} 264 265# Quote args to make them safe in the shell. 266# Usage: quotedlist="$(shell_quote args...)" 267# 268# After building up a quoted list, use it by evaling it inside 269# double quotes, like this: 270# eval "set -- $quotedlist" 271# or like this: 272# eval "\$command $quotedlist \$filename" 273# 274shell_quote() 275( 276 local result='' 277 local arg qarg 278 LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII 279 for arg in "$@" ; do 280 case "${arg}" in 281 '') 282 qarg="''" 283 ;; 284 *[!-./a-zA-Z0-9]*) 285 # Convert each embedded ' to '\'', 286 # then insert ' at the beginning of the first line, 287 # and append ' at the end of the last line. 288 # Finally, elide unnecessary '' pairs at the 289 # beginning and end of the result and as part of 290 # '\'''\'' sequences that result from multiple 291 # adjacent quotes in he input. 292 qarg="$(printf '%s\n' "$arg" | \ 293 ${SED:-sed} -e "s/'/'\\\\''/g" \ 294 -e "1s/^/'/" -e "\$s/\$/'/" \ 295 -e "1s/^''//" -e "\$s/''\$//" \ 296 -e "s/'''/'/g" 297 )" 298 ;; 299 *) 300 # Arg is not the empty string, and does not contain 301 # any unsafe characters. Leave it unchanged for 302 # readability. 303 qarg="${arg}" 304 ;; 305 esac 306 result="${result}${result:+ }${qarg}" 307 done 308 printf '%s\n' "$result" 309) 310 311validate_pwd() 312{ 313 local P="$(pwd)" || return 1 314 315 test -d "${P}" && 316 test -d "${P}/CVS" && 317 test -d "${P}/dist" && 318 test -f "${P}/dist/zone.tab" && 319 test -f "${P}/tzdata2netbsd" || { 320 printf >&2 '%s\n' "Please change to the correct directory" 321 return 1 322 } 323 324 test -f "${P}/CVS/Tag" && { 325 326 # Here (for local use only) if needed for private branch work 327 # insert tests for the value of $(cat "${P}/CVS/Tag") and 328 # allow your private branch tag to pass. Eg: 329 330 # case "$(cat "${P}/CVS/Tag")" in 331 # my-branch-name) return 0;; 332 # esac 333 334 # Do not commit a version of this script modified that way, 335 # (not even on the private branch) - keep it as a local 336 # modified file. (This script will not commit it.) 337 338 printf >&2 '%s\n' \ 339 "This script should be run in a checkout of HEAD only" 340 return 1 341 } 342 343 return 0 344} 345 346findcvsroot() 347{ 348 [ -n "${CVSROOT}" ] && return 0 349 CVSROOT="$( cat ./CVS/Root )" 350 [ -n "${CVSROOT}" ] && return 0 351 echo >&2 "Failed to set CVSROOT value" 352 return 1 353} 354 355mkworkpfx() 356{ 357 mkdir -p "${WORK_PFX}" || fail "Unable to make missing ${WORK_PFX}" 358} 359mkworkdir() 360{ 361 mkdir -p "${WORKDIR}" || fail "Unable to make missing ${WORKDIR}" 362} 363 364fetch() 365{ 366 [ -f "${DISTFILE}" ] || ftp -o "${DISTFILE}" "${DISTURL}" || 367 fail "fetch of ${DISTFILE} failed" 368 [ -f "${SIGFILE}" ] || ftp -o "${SIGFILE}" "${SIGURL}" || 369 fail "fetch of ${SIGFILE} failed" 370 [ -f "${NEWSFILE}" ] || ftp -o "${NEWSFILE}" "${NEWSURL}" || 371 fail "fetch of ${NEWSFILE} failed" 372} 373 374checksig() 375{ 376 { gpg --verify "${SIGFILE}" "${DISTFILE}" 377 echo gpg exit status $? 378 } 2>&1 | tee "${PGPVERIFYLOG}" 379 380 # The output should contain lines that match all the following regexps 381 # 382 while read line; do 383 if ! grep -E -q -e "^${line}\$" "${PGPVERIFYLOG}"; then 384 echo >&2 "Failed to verify signature: ${line}" 385 return 1 386 fi 387 done <<'EOF' 388gpg: Signature made .* using RSA key ID (62AA7E34|44AD418C) 389gpg: Good signature from "Paul Eggert <eggert@cs.ucla.edu>" 390gpg exit status 0 391EOF 392} 393 394extract() 395{ 396 [ -f "${EXTRACTDIR}/zone.tab" ] && return 397 mkdir -p "${EXTRACTDIR}" 398 tar -z -xf "${DISTFILE}" -C "${EXTRACTDIR}" 399} 400 401addnews() 402{ 403 [ -f "${EXTRACTDIR}/NEWS" ] && return 404 cp -p "${NEWSFILE}" "${EXTRACTDIR}"/NEWS 405} 406 407# Find the relevant part of the NEWS file for all releases between 408# OLDVER and NEWVER, and save them to NEWSTRIMFILE. 409# 410trimnews() 411{ 412 [ -s "${NEWSTRIMFILE}" ] && return 413 awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \ 414 ' 415 BEGIN {inrange = 0} 416 /^Release [0-9]+[a-z]+ - .*/ { 417 # "Release <version> - <date>" 418 # Note: must handle transition from 2018z to 2018aa 419 # Assumptions: OLDVER and NEWVER have been sanitized, 420 # and format of NEWS file does not alter (and 421 # contains valid data) 422 inrange = ((length($2) > length(oldver) || \ 423 $2 > oldver) && \ 424 (length($2) < newver || $2 <= newver)) 425 } 426 // { if (inrange) print; } 427 ' \ 428 <"${NEWSFILE}" >"${NEWSTRIMFILE}" 429 echo "tzdata-${NEWVER}" > ${TZDISTDIR}/TZDATA_VERSION 430} 431 432# Create IMPORTMSGFILE from NEWSTRIMFILE, by ignoring some sections, 433# keeping only the first sentence from paragraphs in other sections, 434# and changing the format. 435# 436# The result should be edited by hand before performing a cvs commit. 437# A message to that effect is inserted at the beginning of the file. 438# 439mkimportmsg() 440{ 441 [ -s "${IMPORTMSGFILE}" ] && return 442 { cat <<EOF 443EDIT ME: Edit this file and then delete the lines marked "EDIT ME". 444EDIT ME: This file will be used as a log message for the "cvs commit" that 445EDIT ME: imports tzdata${NEWVER}. The initial contents of this file were 446EDIT ME: generated from ${NEWSFILE}. 447EDIT ME: 448EOF 449 awk -v oldver="${OLDVER}" -v newver="${NEWVER}" \ 450 -v disturl="${DISTURL}" -v newsurl="${NEWSURL}" \ 451 ' 452 BEGIN { 453 bullet = " * "; 454 indent = " "; 455 blankline = 0; 456 goodsection = 0; 457 havesentence = 0; 458 print "Import tzdata"newver" from "disturl; 459 #print "and NEWS file from "newsurl; 460 } 461 /^Release/ { 462 # "Release <version> - <date>" 463 ver = $2; 464 date = gensub(".* - ", "", 1, $0); 465 print ""; 466 print "Summary of changes in tzdata"ver \ 467 " ("date"):"; 468 } 469 /^$/ { blankline = 1; havesentence = 0; } 470 /^ Changes affecting/ { goodsection = 0; } 471 /^ Changes affecting.*time/ { goodsection = 1; } 472 /^ Changes affecting.*data/ { goodsection = 1; } 473 /^ Changes affecting.*documentation/ || \ 474 /^ Changes affecting.*commentary/ { 475 t = gensub("^ *", "", 1, $0); 476 t = gensub("\\.*$", ".", 1, t); 477 print bullet t; 478 goodsection = 0; 479 } 480 /^ .*/ && goodsection { 481 # In a paragraph in a "good" section. 482 # Ignore leading spaces, and ignore anything 483 # after the first sentence. 484 # First line of paragraph gets a bullet. 485 t = gensub("^ *", "", 1, $0); 486 t = gensub("\\. .*", ".", 1, t); 487 if (blankline) print bullet t; 488 else if (! havesentence) print indent t; 489 havesentence = (havesentence || (t ~ "\\.$")); 490 } 491 /./ { blankline = 0; } 492 ' \ 493 <"${NEWSTRIMFILE}" 494 } >"${IMPORTMSGFILE}" 495} 496 497editimportmsg() 498{ 499 if [ -s "${IMPORTMSGFILE}" ] \ 500 && ! grep -q '^EDIT' "${IMPORTMSGFILE}" 501 then 502 return 0 # file has already been edited 503 fi 504 # Pass both IMPORTMSGFILE and NEWSFILE to the editor, so that the 505 # user can easily consult NEWSFILE while editing IMPORTMSGFILE. 506 ${EDITOR} "${IMPORTMSGFILE}" "${NEWSFILE}" 507} 508 509cvsimport() 510{ 511 if [ -e "${IMPORTDONEFILE}" ]; then 512 cat >&2 <<EOF 513The CVS import has already been performed. 514EOF 515 return 0 516 fi 517 if ! [ -s "${IMPORTMSGFILE}" ] \ 518 || grep -q '^EDIT' "${IMPORTMSGFILE}" 519 then 520 cat >&2 <<EOF 521The message file ${IMPORTMSGFILE} 522has not been properly edited. 523Not performing cvs import. 524EOF 525 return 1 526 fi 527 ( cd "${EXTRACTDIR}" && 528 DOIT cvs -d "${CVSROOT}" import -m "$(cat "${IMPORTMSGFILE}")" \ 529 "${REPODIR}" "${CVSBRANCHTAG}" "${CVSNEWTAG}" 530 ) && touch "${IMPORTDONEFILE}" 531} 532 533cvsmerge() 534{ 535 536 cd "${TZDISTDIR}" || exit 1 537 if [ -e "${MERGEDONEFILE}" ]; then 538 cat >&2 <<EOF 539The CVS merge has already been performed. 540EOF 541 return 0 542 fi 543 DOIT cvs -d "${CVSROOT}" update -j"${CVSOLDTAG}" -j"${CVSNEWTAG}" \ 544 && touch "${MERGEDONEFILE}" 545} 546 547resolveconflicts() 548{ 549 cd "${TZDISTDIR}" || exit 1 550 if grep -l '^[<=>][<=>][<=>]' * 551 then 552 cat <<EOF 553There appear to be conflicts in the files listed above. 554Resolve conflicts, then re-run this script. 555EOF 556 return 1 557 fi 558} 559 560cvscommitmerge() 561{ 562 cd "${TZDISTDIR}" || exit 1 563 if grep -l '^[<=>][<=>][<=>]' * 564 then 565 cat >&2 <<EOF 566There still appear to be conflicts in the files listed above. 567Not performing cvs commit. 568EOF 569 return 1 570 fi 571 if [ -e "${COMMITMERGEDONEFILE}" ]; then 572 cat >&2 <<EOF 573The CVS commmit (of the merge result) has already been performed. 574EOF 575 return 0 576 fi 577 DOIT cvs -d "${CVSROOT}" commit -m "Merge tzdata${NEWVER}" \ 578 && touch "${COMMITMERGEDONEFILE}" 579} 580 581extra() 582{ 583 cat <<EOF 584Also do the following: 585 * Edit and commit src/doc/3RDPARTY 586 * Edit and commit src/doc/CHANGES 587 * Edit and commit src/distrib/sets/lists/base/mi 588 * if the set of installed files altered. 589 * Submit pullup requests for all active release branches. 590 * rm -rf ${WORK_PFX} (optional) 591 * Verify that 592 ${UPDATE_FROM} 593 * no longer exists. 594EOF 595} 596 597main() 598{ 599 set -e 600 validate_pwd 601 findcvsroot 602 mkworkpfx 603 argparse "$@" 604 setup_versions 605 mkworkdir 606 fetch 607 checksig 608 extract 609 addnews 610 trimnews 611 mkimportmsg 612 editimportmsg 613 cvsimport 614 cvsmerge 615 resolveconflicts 616 cvscommitmerge 617 rm -f "${UPDATE_FROM}" 618 extra 619} 620 621main "$@" 622