# Copyright (c) 2017 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Johnny C. Lam. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # NAME # version.subr -- compare version strings # # SYNOPSIS # echo ${TASK_VERSION} # # task_version_check [* | < | <= | = | => | >] ... # # task_version_compare [-v] # # DESCRIPTION # ${TASK_VERSION} is the version number of the installed task modules. # # The task_version_check function checks whether # satisfies the constraints specified in the remaining parameters. # The remaining parameters are contraints of pairs of an operator and # a version number, and the constraints must all be met for a # successful version check. # # The task_version_compare function compares the two version strings. # # A version string has the following form: # # [+] # # represents the version of the software and SHOULD be # identical to the version string used by the maintainer of the # software. # # is a version that is independent of the version of the # software, and is used to differentiate two version strings with the # same . # # Both and are an alternating sequence of numeric and # non-numeric components. Each component MAY contain any # printable-ASCII character except for '/' (slash, ASCII 47), '-' # (dash, ASCII 45), and '+' (plus, ASCII 43). # # The magic string "nb" MAY be used as a synonym for '+', used to # separate and in a version string. # # ALGORITHM # The comparison algorithm is to compare corresponding components of # the two version strings, beginning with the leftmost components, and # to compare the next pair of components if the current ones match. # If the components are numeric, then do a numeric comparison; # otherwise, do a string comparison. # # There are magic strings that may be found in version numbers, listed # below in sorting order from lowest to highest: # # ~[string] Tilde (ASCII 126) string; sorts before a release # version. The string after the '~' is optional. # # alpha Equates to "alpha version" and is a synonym for # "~alpha". # # beta Equates to "beta version" and is a synonym for # "~beta". # # rc Equates to "release candidate" and is a synonym for # "~rc". # # pre Equates to "pre-release" and is a synonym for a # release candidate. # # . Period (ASCII 46); the usual separator between # numeric components. # # _ Underscore (ASCII 95); a synonym for '.' (period). # # pl Equates to "patch level" and is a synonym for '.' # (period). # # Additionally, single alphabetic characters sort in the same place as # their numeric counterparts, so "1.2e" and "1.2.5" are equivalent with # respect to their sorting order. # # An empty string for the version number is the "null" version and sorts # as less than all other version numbers. # # RETURN VALUES # The task_version_check function returns 0 if all of the contraints # are met, and >0 if they are not. # # The task_version_compare function has the following return values: # # 0 if is less than # 1 if equals # 2 if is greater than # # ENVIRONMENT # The following variables are used if they are set: # # AWK The name or path to the awk(1) utility. # __task_version__="yes" # The version of the installed task modules. TASK_VERSION=@TASK_VERSION@ task_version_check() { [ $# -ge 3 ] || return 127 local check_version="$1"; shift local cmp_result local operator= local version= for version; do if [ -z "$operator" ]; then operator=$version continue fi task_version_compare "$check_version" "$version" cmp_result=$? case $operator in "*") # any case $cmp_result in 0|1|2) : "match" ;; *) return 1 ;; esac ;; "<") # less than case $cmp_result in 0) : "match" ;; *) return 1 ;; esac ;; "<=") # less than or equal to case $cmp_result in 0|1) : "match" ;; *) return 1 ;; esac ;; "=") # equal to case $cmp_result in 1) : "match" ;; *) return 1 ;; esac ;; "!=") # not equal to case $cmp_result in 0|2) : "match" ;; *) return 1 ;; esac ;; ">=") # greater than or equal to case $cmp_result in 1|2) : "match" ;; *) return 1 ;; esac ;; ">") # greater than case $cmp_result in 2) : "match" ;; *) return 1 ;; esac ;; esac operator= done [ -z "$operator" ] || return 127 return 0 } task_version_compare() { # Returns 0 if version1 < version2. # Returns 1 if version1 = version2. # Returns 2 if version1 > version2. # Decompose each version string into and components. local verbose= local arg local OPTIND=1 while getopts ":v" arg "$@"; do case $arg in v) verbose="-v" ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -eq 2 ] || return 127 local version1="$1"; shift local version2="$1"; shift local swver1 pkgver1 local swver2 pkgver2 case $version1 in *"+"*) swver1=${version1%+*} pkgver1=${version1#$swver1+} ;; *"nb"*) swver1=${version1%nb*} pkgver1=${version1#${swver1}nb} ;; *) swver1=$version1 pkgver1= ;; esac case $version2 in *"+"*) swver2=${version2%+*} pkgver2=${version2#$swver2+} ;; *"nb"*) swver2=${version2%nb*} pkgver2=${version2#${swver2}nb} ;; *) swver2=$version2 pkgver2= ;; esac [ -z "$verbose" ] || echo "> compare \"$version1\" vs. \"$version2\"" # Compare $swver1 and $swver2. _task_version_compare $verbose "$swver1" "$swver2" local result=$? case $result in 1) : "fall through to comparison" ;; *) return $result ;; esac # Compare $pkgver1 and $pkgver2. _task_version_compare $verbose "$pkgver1" "$pkgver2" } _task_version_compare() { # Returns 0 if version1 < version2. # Returns 1 if version1 = version2. # Returns 2 if version1 > version2. # Returns >2 if an error occurs. local echo=":" local arg local OPTIND=1 while getopts ":v" arg "$@"; do case $arg in v) echo="echo" ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -eq 2 ] || return 127 local version1="$1"; shift local version2="$1"; shift # Verify version strings. case $version1 in *"/"*|*"-"*|*"+"*) return 3 ;; esac case $version2 in *"/"*|*"-"*|*"+"*) return 3 ;; esac $echo " > compare: \"$version1\" vs. \"$version2\"" # Same versions. if [ "$version1" = "$version2" ]; then $echo " > equal"; return 1 fi # Null versions. if [ -z "$version1" ]; then $echo " > less than"; return 0 fi if [ -z "$version2" ]; then $echo " > more than"; return 2 fi # Break down version numbers for comparisions. # # A version number is a sequence of numbers separated by non-numeric # strings. # # Always start version comparisons with a non-numeric string. case $version1 in [0-9]*) version1=".$version1" ;; *) version1="$version1" ;; esac case $version2 in [0-9]*) version2=".$version2" ;; *) version2="$version2" ;; esac local number1 str1 value1 tilde1 substr1 local number2 str2 value2 tilde2 substr2 local mode="string" while : ; do case $mode in number) # Set number to the numeric component before the next # non-numeric string, and set version to the remaining # version after the leading numeric component. # number1=${version1%%[!0-9]*} case $version1 in *[!0-9]*) version1=${version1#$number1} ;; *) version1= ;; esac number2=${version2%%[!0-9]*} case $version2 in *[!0-9]*) version2=${version2#$number2} ;; *) version2= ;; esac $echo " > \"$number1 | $version1\" vs. \"$number2 | $version2\"" if [ -z "$number1" -a -z "$number2" ]; then # Both version numbers terminate. break elif [ -z "$number1" ]; then $echo " > less than"; return 0 elif [ -z "$number2" ]; then $echo " > more than"; return 2 elif [ $number1 -lt $number2 ]; then $echo " > less than"; return 0 elif [ $number1 -gt $number2 ]; then $echo " > more than"; return 2 fi mode="string" ;; string) # Set str to the non-numeric string before the next # numeric component, and set version to the remaining # version after the leading non-numeric string. # str1=${version1%%[0-9]*} case $version1 in *[0-9]*) version1=${version1#$str1} ;; *) version1= ;; esac str2=${version2%%[0-9]*} case $version2 in *[0-9]*) version2=${version2#$str2} ;; *) version2= ;; esac # # Alphabetic characters sort in the same place as their # numeric counterparts, e.g., 1.2e = 1.2.5. Set str # to a dot "." and push the numeric counterpart back # onto the front of version. # case $str1 in [A-Za-z]) case $str1 in [Aa]) value1=1 ;; [Nn]) value1=14 ;; [Bb]) value1=2 ;; [Oo]) value1=15 ;; [Cc]) value1=3 ;; [Pp]) value1=16 ;; [Dd]) value1=4 ;; [Qq]) value1=17 ;; [Ee]) value1=5 ;; [Rr]) value1=18 ;; [Ff]) value1=6 ;; [Ss]) value1=19 ;; [Gg]) value1=7 ;; [Tt]) value1=20 ;; [Hh]) value1=8 ;; [Uu]) value1=21 ;; [Ii]) value1=9 ;; [Vv]) value1=22 ;; [Jj]) value1=10 ;; [Ww]) value1=23 ;; [Kk]) value1=11 ;; [Xx]) value1=24 ;; [Ll]) value1=12 ;; [Yy]) value1=25 ;; [Mm]) value1=13 ;; [Zz]) value1=26 ;; esac case $version1 in "") version1="$value1" ;; *) version1="$value1.$version1" ;; esac str1="." ;; esac case $str2 in [A-Za-z]) case $str2 in [Aa]) value2=1 ;; [Nn]) value2=14 ;; [Bb]) value2=2 ;; [Oo]) value2=15 ;; [Cc]) value2=3 ;; [Pp]) value2=16 ;; [Dd]) value2=4 ;; [Qq]) value2=17 ;; [Ee]) value2=5 ;; [Rr]) value2=18 ;; [Ff]) value2=6 ;; [Ss]) value2=19 ;; [Gg]) value2=7 ;; [Tt]) value2=20 ;; [Hh]) value2=8 ;; [Uu]) value2=21 ;; [Ii]) value2=9 ;; [Vv]) value2=22 ;; [Jj]) value2=10 ;; [Ww]) value2=23 ;; [Kk]) value2=11 ;; [Xx]) value2=24 ;; [Ll]) value2=12 ;; [Yy]) value2=25 ;; [Mm]) value2=13 ;; [Zz]) value2=26 ;; esac case $version2 in "") version2="$value2" ;; *) version2="$value2.$version2" ;; esac str2="." ;; esac # # Some magic values that sort before a release version. # case $str1 in alpha|beta|rc) str1="~$str1" ;; pre) str1="~rc" ;; # synonym for "rc" esac case $str2 in alpha|beta|rc) str2="~$str2" ;; pre) str2="~rc" ;; # synonym for "rc" esac # # Early short-circuit comparisons. # case $str1/$str2 in "~"*/[!~]*) $echo " > less than"; return 0 ;; [!~]*/"~"*) $echo " > more than"; return 2 ;; "~"*/"~"*|[!~]*/[!~]*) : "fall through to string comparison" ;; esac # Compare successive substrings separated by '~'. while : ; do # Set substr to the string before the next '~' # character, and set str to the remaining string # starting from the next '~' character. # case $str1 in "~"*) tilde1="~"; str1=${str1#~} ;; *) tilde1= ;; esac case $str1 in *"~"*) substr1=${str1%%~*} str1=${str1#$substr1} ;; *) substr1=$str1 str1= ;; esac substr1="$tilde1$substr1" case $str2 in "~"*) tilde2="~"; str2=${str2#~} ;; *) tilde2= ;; esac case $str2 in *"~"*) substr2=${str2%%~*} str2=${str2#$substr2} ;; *) substr2=$str2 str2= ;; esac substr2="$tilde2$substr2" # # Assign numerical values to the strings for # comparison. We can't do a straight string # comparison because some of the strings are # magic tokens. # case $substr1 in ~*) value1=100 ;; "") value1=400 ;; [.]|_|pl) value1=500 ;; *) value1=1000 ;; esac case $substr2 in ~*) value2=100 ;; "") value2=400 ;; [.]|_|pl) value2=500 ;; *) value2=1000 ;; esac $echo " > \"$substr1 ($value1) / $str1 | $version1\" vs. \"$substr2 ($value2) / $str2 | $version2\"" if [ $value1 -eq 1000 -a $value2 -eq 1000 ] || [ $value1 -eq 100 -a $value2 -eq 100 ]; then # Non-empty string comparisons. if _task_string_compare "$substr1" "<" "$substr2"; then $echo " > less than"; return 0 elif _task_string_compare "$substr1" ">" "$substr2"; then $echo " > more than"; return 2 fi elif [ $value1 -eq 400 -a $value2 -eq 400 ]; then # Both version numbers terminate. break elif [ $value1 -lt $value2 ]; then $echo " > less than"; return 0 elif [ $value1 -gt $value2 ]; then $echo " > more than"; return 2 fi done mode="number" ;; esac done $echo " > equal"; return 1 } _task_string_compare() { : ${AWK:=awk} [ $# -eq 3 ] || return 127 local str1="$1"; shift local op="$1"; shift local str2="$1"; shift case $op in '<'|'>'|'='|'!=') test "$str1" "$op" "$str2" 2>/dev/null ;; '<=') test "$str1" "<" "$str2" 2>/dev/null && test "$str1" "=" "$str2" 2>/dev/null ;; '>=') test "$str1" ">" "$str2" 2>/dev/null && test "$str1" "=" "$str2" 2>/dev/null ;; *) # invalid operator return 127 ;; esac local result=$? case $result in 0|1) return $result ;; *) # Fall back to using awk(1) for lexographic comparison. # Convert operator to the AWK-equivalent if needed. case $op in '=') op="==" ;; *) : "other operators are the same in AWK" ;; esac ${AWK} -v STR1="$str1" -v STR2="$str2" \ 'BEGIN { exit ( (STR1 '"$op"' STR2) ? 0 : 1 ) }' ;; esac }