├── .gitignore ├── meson_options.txt ├── AUTHORS ├── meson.build ├── consoletype.1 ├── consoletype.c ├── functions ├── portage.sh ├── experimental.sh └── rc.sh ├── COPYING ├── functions.sh └── test-functions /.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.bz2 2 | consoletype 3 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('tests', type : 'boolean', value : true, 2 | description : 'Build tests' 3 | ) 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following list of gentoo-functions authors is ordered by surname. 2 | 3 | - Anthony G. Basile 4 | - Thomas D 5 | - Jacob Floyd 6 | - Mike Frysinger 7 | - Mike Gilbert 8 | - William Hubbs 9 | - Sam James 10 | - Kerin Millar 11 | - Ulrich Müller 12 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'gentoo-functions', 'c', 3 | version: '1.7.3', 4 | license: 'GPL-2.0-only', 5 | default_options : [ 6 | 'warning_level=2', 7 | 'c_std=gnu11', 8 | ] 9 | ) 10 | 11 | install_data( 12 | 'functions.sh', 13 | install_dir: 'lib/gentoo' 14 | ) 15 | 16 | install_subdir( 17 | 'functions', 18 | install_dir: 'lib/gentoo' 19 | ) 20 | 21 | cc = meson.get_compiler('c') 22 | 23 | executable( 24 | 'consoletype', 25 | 'consoletype.c', 26 | install: true 27 | ) 28 | 29 | install_man( 30 | 'consoletype.1', 31 | ) 32 | 33 | do_tests = get_option('tests') 34 | if do_tests 35 | test( 36 | 'test-functions', files('test-functions'), 37 | workdir : meson.current_source_dir(), 38 | protocol : 'tap', 39 | verbose : true 40 | ) 41 | endif 42 | -------------------------------------------------------------------------------- /consoletype.1: -------------------------------------------------------------------------------- 1 | .TH CONSOLETYPE 1 "Red Hat, Inc" "RH" \" -*- nroff -*- 2 | .SH NAME 3 | .B consoletype 4 | \- print type of the console connected to standard input 5 | .SH SYNOPSIS 6 | .B consoletype [\fIstdout\fR] 7 | .SH DESCRIPTION 8 | .B consoletype 9 | prints the type of console connected to standard input. It prints 10 | .I vt 11 | if console is a virtual terminal (/dev/tty* or /dev/console device if not on 12 | a serial console), 13 | .I serial 14 | if standard input is a serial console (/dev/console or /dev/ttyS*) and 15 | .I pty 16 | if standard input is a pseudo terminal. 17 | .SH RETURN VALUE 18 | .B consoletype 19 | When passed no arguments returns 20 | .TP 21 | .I 0 22 | if on virtual terminal 23 | .TP 24 | .I 1 25 | if on serial console 26 | .TP 27 | .I 2 28 | if on a pseudo terminal. 29 | .TP 30 | When passed the \fIstdout\fR argument, returns 31 | .TP 32 | .I 0 33 | in all cases. 34 | -------------------------------------------------------------------------------- /consoletype.c: -------------------------------------------------------------------------------- 1 | /* 2 | * consoletype.c 3 | * simple app to figure out whether the current terminal 4 | * is serial, console (vt), or remote (pty). 5 | * 6 | * Copyright 1999-2020 Gentoo Authors 7 | * Distributed under the terms of the GNU General Public License v2 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #if defined(__linux__) 17 | #include 18 | #endif 19 | 20 | enum termtype { 21 | IS_VT = 0, 22 | IS_SERIAL = 1, 23 | IS_PTY = 2, 24 | IS_UNK = 3 25 | }; 26 | 27 | const char * const tty_names[] = { 28 | "vt", 29 | "serial", 30 | "pty", 31 | "unknown" 32 | }; 33 | 34 | static inline int check_ttyname(void) 35 | { 36 | char *tty = ttyname(0); 37 | 38 | if (tty == NULL) 39 | return IS_UNK; 40 | 41 | if (strncmp(tty, "/dev/", 5) == 0) 42 | tty += 5; 43 | 44 | if (!strncmp (tty, "ttyS", 4) || !strncmp (tty, "cuaa", 4)) 45 | return IS_SERIAL; 46 | else if (!strncmp (tty, "pts/", 4) || !strncmp (tty, "ttyp", 4)) 47 | return IS_PTY; 48 | else if (!strncmp (tty, "tty", 3)) 49 | return IS_VT; 50 | else 51 | return IS_UNK; 52 | } 53 | 54 | static inline int check_devnode(void) 55 | { 56 | #if defined(__linux__) 57 | int maj; 58 | struct stat sb; 59 | 60 | fstat(0, &sb); 61 | maj = major(sb.st_rdev); 62 | if (maj != 3 && (maj < 136 || maj > 143)) { 63 | #if defined(TIOCLINUX) 64 | unsigned char twelve = 12; 65 | if (ioctl (0, TIOCLINUX, &twelve) < 0) 66 | return IS_SERIAL; 67 | #endif 68 | return IS_VT; 69 | } else 70 | return IS_PTY; 71 | #endif 72 | return IS_UNK; 73 | } 74 | 75 | int main(int argc, char *argv[]) 76 | { 77 | int rc; 78 | int type = check_ttyname(); 79 | if (type == IS_UNK) 80 | type = check_devnode(); 81 | puts(tty_names[type]); 82 | if (argc > 1 && strcmp(argv[1], "stdout") == 0) 83 | rc = 0; 84 | else 85 | rc = type; 86 | return rc; 87 | } 88 | -------------------------------------------------------------------------------- /functions/portage.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Gentoo Authors 2 | # Distributed under the terms of the GNU General Public License v2 3 | # shellcheck shell=sh disable=3043 4 | 5 | # This file contains alternative implementations for some of the functions and 6 | # utilities provided by portage and its supporting eclasses. Please refer to 7 | # ../functions.sh for coding conventions. 8 | 9 | # The following variables affect initialisation and/or function behaviour. 10 | 11 | # IFS : multiple message operands are joined by its first character 12 | # RC_GOT_FUNCTIONS : whether the rc module may be used for printing messages 13 | 14 | #------------------------------------------------------------------------------# 15 | 16 | # 17 | # Prints a diagnostic message prefixed with the basename of the running script 18 | # before exiting. It shall preserve the value of $? as it was at the time of 19 | # invocation unless its value was 0, in which case the exit status shall be 1. 20 | # 21 | die() 22 | { 23 | case $? in 24 | 0) 25 | genfun_status=1 26 | ;; 27 | *) 28 | genfun_status=$? 29 | esac 30 | warn "$@" 31 | exit "${genfun_status}" 32 | } 33 | 34 | # 35 | # Takes the positional parameters as the definition of a simple command then 36 | # prints the command as an informational message with einfo before executing it. 37 | # Should the command fail, a diagnostic message shall be printed and the shell 38 | # be made to exit by calling the die function. 39 | # 40 | edo() 41 | { 42 | genfun_cmd=$(quote_args "$@") 43 | if [ "${RC_GOT_FUNCTIONS}" ]; then 44 | einfo "Executing: ${genfun_cmd}" 45 | else 46 | printf 'Executing: %s\n' "${genfun_cmd}" 47 | fi 48 | "$@" || die "Failed to execute command: ${genfun_cmd}" 49 | } 50 | 51 | # 52 | # This is based on the eqatag function defined by isolated-functions.sh in 53 | # portage. If the first parameter is the -v option, it shall be disregarded. 54 | # Discounting said option, at least one parameter is required, which shall be 55 | # taken as a tag name. Thereafter, zero or more parameters shall be accepted in 56 | # the form of "key=val", followed by zero or more parameters beginning with a 57 | # . An object shall be composed in which the tag is the value of a "tag" 58 | # key, the key/value pairs the value of a "data" key, and the -prefixed 59 | # parameters the value of a "files" key. The resulting object shall be rendered 60 | # as JSON by jq(1) before being logged by the logger(1) utility. 61 | # 62 | eqatag() 63 | { 64 | local arg i json positional tag 65 | 66 | case ${genfun_has_jq} in 67 | 0) 68 | return 1 69 | ;; 70 | '') 71 | if ! hash jq 2>/dev/null; [ "$(( genfun_has_jq = $? ))" -eq 0 ]; then 72 | warn "eqatag: this function requires that jq be installed" 73 | return 1 74 | fi 75 | esac 76 | # Acknowledge the -v option for isolated-functions API compatibility. 77 | if [ "$1" = "-v" ]; then 78 | shift 79 | fi 80 | if [ "$#" -eq 0 ]; then 81 | warn "eqatag: no tag specified" 82 | return 1 83 | fi 84 | positional=0 85 | tag=$1 86 | shift 87 | i=0 88 | for arg; do 89 | if [ "$(( i += 1 ))" -eq 1 ]; then 90 | set -- 91 | fi 92 | case ${arg} in 93 | [!=/]*=?*) 94 | if [ "${positional}" -eq 1 ]; then 95 | _warn_for_args eqatag "${arg}" 96 | return 1 97 | fi 98 | set -- "$@" --arg "${arg%%=*}" "${arg#*=}" 99 | ;; 100 | /*) 101 | if [ "${positional}" -eq 0 ]; then 102 | set -- "$@" --args -- 103 | positional=1 104 | fi 105 | set -- "$@" "${arg}" 106 | ;; 107 | *) 108 | _warn_for_args eqatag "${arg}" 109 | return 1 110 | esac 111 | done 112 | json=$( 113 | jq -cn '{ 114 | eqatag: { 115 | tag: $ARGS.named["=tag"], 116 | data: $ARGS.named | with_entries(select(.key | startswith("=") | not)), 117 | files: $ARGS.positional 118 | } 119 | }' --arg "=tag" "${tag}" "$@" 120 | ) \ 121 | && logger -p user.debug -t "${0##*/}" -- "${json}" 122 | } 123 | 124 | # 125 | # Prints a QA warning message, provided that EINFO_QUIET is false. If printed, 126 | # the message shall also be conveyed to the esyslog function. For now, this is 127 | # implemented merely as an ewarn wrapper. 128 | # 129 | eqawarn() 130 | { 131 | if [ "${RC_GOT_FUNCTIONS}" ]; then 132 | ewarn "$@" 133 | else 134 | warn "$@" 135 | fi 136 | } 137 | -------------------------------------------------------------------------------- /functions/experimental.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Gentoo Authors 2 | # Distributed under the terms of the GNU General Public License v2 3 | # shellcheck shell=sh disable=3043 4 | 5 | # This file contains functions considered experimental in nature. Any functions 6 | # defined here may eventually be promoted to the core library or to a distinct 7 | # module. They may also be dropped without warning, either because they were 8 | # not considered as being sufficiently within the scope of gentoo-functions as 9 | # a project or because they were deemed to be insufficiently useful. As such, it 10 | # serves as a staging ground for new ideas. Note that GENFUN_API_LEVEL must 11 | # never be incremented on account of any changes made to this module. 12 | 13 | warn "sourcing the experimental module from gentoo-functions; no stability guarantee is provided" 14 | 15 | # 16 | # Considers the first parameter as an URL then attempts to fetch it with either 17 | # curl(1) or wget(1). If the URL does not contain a scheme then the https:// 18 | # scheme shall be presumed. Both utilities shall be invoked in a manner that 19 | # suppresses all output unless an error occurs, and whereby HTTP redirections 20 | # are honoured. Upon success, the body of the response shall be printed to the 21 | # standard output. Otherwise, the return value shall be greater than 0. 22 | # 23 | fetch() 24 | { 25 | if hash curl 2>/dev/null; then 26 | fetch() 27 | { 28 | if [ "$#" -gt 0 ]; then 29 | # Discard any extraneous parameters. 30 | set -- "$1" 31 | fi 32 | curl -f -sS -L --connect-timeout 10 --proto-default https -- "$@" 33 | } 34 | elif hash wget 2>/dev/null; then 35 | fetch() 36 | { 37 | if [ "$#" -gt 0 ]; then 38 | # Discard any extraneous parameters. 39 | case $1 in 40 | ''|ftp://*|ftps://*|https://*) 41 | set -- "$1" 42 | ;; 43 | *) 44 | set -- "https://$1" 45 | esac 46 | fi 47 | wget -nv -O - --connect-timeout 10 -- "$@" 48 | } 49 | else 50 | warn "fetch: this function requires that curl or wget be installed" 51 | return 127 52 | fi 53 | 54 | fetch "$@" 55 | } 56 | 57 | # 58 | # Expects three parameters, all of which must be integers, and determines 59 | # whether the first is numerically greater than or equal to the second, and 60 | # numerically lower than or equal to the third. 61 | # 62 | int_between() 63 | { 64 | if [ "$#" -lt 3 ]; then 65 | warn "int_between: too few arguments (got $#, expected 3)" 66 | false 67 | elif ! is_int "$2" || ! is_int "$3"; then 68 | _warn_for_args int_between "$@" 69 | false 70 | else 71 | is_int "$1" && [ "$1" -ge "$2" ] && [ "$1" -le "$3" ] 72 | fi 73 | } 74 | 75 | # 76 | # Returns 0 provided that two conditions hold. Firstly, that the standard input 77 | # is connected to a tty. Secondly, that the standard output has not been closed. 78 | # This technique is loosely based on the IO::Interactive::Tiny module from CPAN. 79 | # 80 | is_interactive() 81 | { 82 | test -t 0 && { true 3>&1; } 2>/dev/null 83 | } 84 | 85 | # 86 | # Collects the intersection of the parameters up to - but not including - a 87 | # sentinel value then determines whether the resulting set is a subset of the 88 | # intersection of the remaining parameters. If the SENTINEL variable is set, it 89 | # shall be taken as the value of the sentinel. Otherwise, the value of the 90 | # sentinel shall be defined as . If the sentinel value 91 | # is not encountered or if either set is empty then the return value shall be 92 | # greater than 1. 93 | # 94 | is_subset() 95 | { 96 | SENTINEL=${SENTINEL-'--'} awk -f - -- "$@" <<-'EOF' 97 | BEGIN { 98 | argc = ARGC 99 | ARGC = 1 100 | for (i = 1; i < argc; i++) { 101 | word = ARGV[i] 102 | if (word == ENVIRON["SENTINEL"]) { 103 | break 104 | } else { 105 | set1[word] 106 | } 107 | } 108 | if (i == 1 || argc - i < 2) { 109 | exit 1 110 | } 111 | for (i++; i < argc; i++) { 112 | word = ARGV[i] 113 | set2[word] 114 | } 115 | for (word in set2) { 116 | delete set1[word] 117 | } 118 | for (word in set1) { 119 | exit 1 120 | } 121 | } 122 | EOF 123 | } 124 | 125 | # 126 | # Continuously reads lines from the standard input, prepending each with a 127 | # timestamp before printing to the standard output. Timestamps shall be in the 128 | # format of "%FT%T%z", per strftime(3). Output buffering shall not be employed. 129 | # 130 | prepend_ts() 131 | { 132 | if hash gawk 2>/dev/null; then 133 | prepend_ts() 134 | { 135 | gawk '{ print strftime("%FT%T%z"), $0; fflush(); }' 136 | } 137 | elif hash ts 2>/dev/null; then 138 | prepend_ts() 139 | { 140 | ts '%FT%T%z' 141 | } 142 | elif bash -c '(( BASH_VERSINFO >= 4 ))' 2>/dev/null; then 143 | prepend_ts() 144 | { 145 | bash -c 'while read -r; do printf "%(%FT%T%z)T %s\n" -1 "${REPLY}"; done' 146 | } 147 | else 148 | warn "prepend_ts: this function requires that either bash, gawk or moreutils be installed" 149 | return 1 150 | fi 151 | 152 | prepend_ts 153 | } 154 | 155 | # 156 | # Expects three parameters and determines whether the first is lexicographically 157 | # greater than or equal to the second, and lexicographically lower than or equal 158 | # to the third. The effective system collation shall affect the results, given 159 | # the involvement of the sort(1) utility. 160 | # 161 | str_between() 162 | { 163 | local i 164 | 165 | if [ "$#" -ne 3 ]; then 166 | warn "str_between: wrong number of arguments (got $#, expected 3)" 167 | false 168 | else 169 | set -- "$2" "$1" "$3" 170 | i=0 171 | # shellcheck disable=2034 172 | printf '%s\n' "$@" | 173 | sort | 174 | while IFS= read -r line; do 175 | eval "[ \"\${line}\" = \"\$$(( i += 1 ))\" ]" || return 176 | done 177 | fi 178 | } 179 | 180 | # 181 | # Takes the first parameter as a string (s), the second parameter as a numerical 182 | # position (m) and, optionally, the third parameter as a numerical length (n). 183 | # It shall then print a terminated substring of s that is at most, n 184 | # characters in length and which begins at position m, numbering from 1. If n is 185 | # omitted, or if n specifies more characters than are left in the string, the 186 | # length of the substring shall be limited by the length of s. The function 187 | # shall return 0 provided that none of the parameters are invalid. 188 | # 189 | substr() 190 | { 191 | local i str 192 | 193 | if [ "$#" -lt 2 ]; then 194 | warn "substr: too few arguments (got $#, expected at least 2)" 195 | return 1 196 | elif ! is_int "$2"; then 197 | _warn_for_args substr "$2" 198 | return 1 199 | elif [ "$#" -ge 3 ]; then 200 | if ! is_int "$3"; then 201 | _warn_for_args substr "$3" 202 | return 1 203 | elif [ "$3" -lt 0 ]; then 204 | set -- "$1" "$2" 0 205 | fi 206 | fi 207 | str=$1 208 | i=0 209 | while [ "$(( i += 1 ))" -lt "$2" ]; do 210 | str=${str#?} 211 | done 212 | i=0 213 | while [ "${#str}" -gt "${3-${#str}}" ]; do 214 | str=${str%?} 215 | done 216 | if [ "${#str}" -gt 0 ]; then 217 | printf '%s\n' "${str}" 218 | fi 219 | } 220 | 221 | # 222 | # Takes the first parameter as either a relative pathname or an integer 223 | # referring to a number of iterations. To be recognised as a pathname, the first 224 | # four characters must form the special prefix, ".../". It recurses upwards from 225 | # the current directory until either the relative pathname is found to exist, 226 | # the specified number of iterations has occurred, or the root directory is 227 | # encountered. In the event that the root directory is reached without either of 228 | # the first two conditions being satisfied, the return value shall be 1. 229 | # Otherwise, the value of PWD shall be printed to the standard output. 230 | # 231 | up() 232 | { 233 | local i 234 | 235 | i=0 236 | while [ "${PWD}" != / ]; do 237 | chdir ../ 238 | case $1 in 239 | .../*) 240 | test -e "${1#.../}" 241 | ;; 242 | *) 243 | test "$(( i += 1 ))" -eq "$1" 244 | esac \ 245 | && pwd && return 246 | done 247 | } 248 | -------------------------------------------------------------------------------- /functions/rc.sh: -------------------------------------------------------------------------------- 1 | # Copyright 1999-2024 Gentoo Authors 2 | # Distributed under the terms of the GNU General Public License v2 3 | # shellcheck shell=sh disable=3013,3043 4 | 5 | # This file contains alternative implementations for some of the functions and 6 | # utilities provided by OpenRC. Please refer to ../functions.sh for coding 7 | # conventions. 8 | 9 | # The following variables affect initialisation and/or function behaviour. 10 | 11 | # EERROR_QUIET : whether error printing functions should be silenced 12 | # EINFO_LOG : whether printing functions should call esyslog() 13 | # EINFO_QUIET : whether info message printing functions should be silenced 14 | # EINFO_VERBOSE : whether v-prefixed functions should do anything 15 | # IFS : multiple message operands are joined by its first character 16 | # INSIDE_EMACS : whether to work around an emacs-specific bug in _eend() 17 | # NO_COLOR : whether colored output should be suppressed 18 | # RC_NOCOLOR : like NO_COLOR but deprecated 19 | # TERM : whether to work around an emacs-specific bug in _eend() 20 | # TEST_GENFUNCS : used for testing the behaviour of get_bootparam() 21 | 22 | #------------------------------------------------------------------------------# 23 | 24 | # 25 | # Prints a message indicating the onset of a given process, provided that 26 | # EINFO_QUIET is false. It is expected that eend eventually be called, so as to 27 | # indicate whether the process completed successfully or not. 28 | # 29 | ebegin() 30 | { 31 | local msg 32 | 33 | if ! yesno "${EINFO_QUIET}"; then 34 | msg=$* 35 | while _ends_with_newline "${msg}"; do 36 | msg=${msg%"${genfun_newline}"} 37 | done 38 | _eprint "${GOOD}" "${msg} ...${genfun_newline}" 39 | fi 40 | } 41 | 42 | # 43 | # Prints an indicator to convey the completion of a given process, provided that 44 | # EINFO_QUIET is false. It is expected that it be paired with an earlier call to 45 | # ebegin. The first parameter shall be taken as an exit status value, making it 46 | # possible to distinguish between success and failure. If unspecified, it shall 47 | # default to 0. The remaining parameters, if any, shall be taken as a diagnostic 48 | # message to convey as an error where the exit status is not 0. 49 | # 50 | eend() 51 | { 52 | : "${genfun_caller:=eend}" 53 | _eend eerror "$@" 54 | } 55 | 56 | # 57 | # Declare the eerror, einfo and ewarn functions. These wrap errorn, einfon and 58 | # ewarnn respectively, the difference being that a newline is appended. 59 | # 60 | for _ in eerror einfo ewarn; do 61 | eval " 62 | $_ () 63 | { 64 | ${_}n \"\${*}\${genfun_newline}\" 65 | } 66 | " 67 | done 68 | 69 | # 70 | # Prints an error message without appending a newline, provided that 71 | # EERROR_QUIET is false. If printed, the message shall also be conveyed to the 72 | # esyslog function. 73 | # 74 | eerrorn() 75 | { 76 | if ! yesno "${EERROR_QUIET}"; then 77 | _eprint "${BAD}" "$@" >&2 78 | esyslog "daemon.err" "${0##*/}" "$@" 79 | fi 80 | return 1 81 | } 82 | 83 | # 84 | # Decreases the level of indentation used by various printing functions. If no 85 | # numerical parameter is given, or if it is negative, increase by 2 spaces. 86 | # 87 | eindent() 88 | { 89 | if ! is_int "$1" || [ "$1" -le 0 ]; then 90 | set -- 2 91 | fi 92 | _esetdent "$(( ${#genfun_indent} + $1 ))" 93 | } 94 | 95 | # 96 | # Prints an informational message without appending a newline, provided that 97 | # EINFO_QUIET is false. 98 | # 99 | einfon() 100 | { 101 | if ! yesno "${EINFO_QUIET}"; then 102 | _eprint "${GOOD}" "$@" 103 | fi 104 | } 105 | 106 | # 107 | # Decreases the level of indentation used by various printing functions. If no 108 | # numerical parameter is given, or if it is negative, decrease by 2 spaces. 109 | # 110 | eoutdent() 111 | { 112 | if ! is_int "$1" || [ "$1" -le 0 ]; then 113 | set -- 2 114 | fi 115 | _esetdent "$(( ${#genfun_indent} - $1 ))" 116 | } 117 | 118 | # 119 | # Invokes the logger(1) utility, provided that EINFO_LOG is true. The first 120 | # parameter shall be taken as a priority level, the second as the message tag, 121 | # and the remaining parameters as the message to be logged. As a special case, 122 | # the value of EINFO_LOG shall be treated as if were false in the event that it 123 | # is equal to the value of RC_SERVICE. The reason for this is that, as of the 124 | # time of writing, openrc-run(8) defines and uses EINFO_LOG in a way that is 125 | # at odds with gentoo-functions. 126 | # 127 | esyslog() 128 | { 129 | local pri tag msg 130 | 131 | if [ "$#" -lt 2 ]; then 132 | warn "esyslog: too few arguments (got $#, expected at least 2)" 133 | return 1 134 | elif [ "${EINFO_LOG}" != "${RC_SERVICE}" ] && yesno "${EINFO_LOG}"; then 135 | pri=$1 tag=$2 136 | shift 2 137 | msg=$* 138 | if _is_visible "${msg}"; then 139 | # The -p and -t options are standard as of POSIX-1.2024. 140 | logger -p "${pri}" -t "${tag}" -- "${msg}" 141 | fi 142 | fi 143 | } 144 | 145 | # 146 | # Prints a warning message without appending a newline, provided that 147 | # EINFO_QUIET is false. If printed, the message shall also be conveyed to the 148 | # esyslog function. 149 | # 150 | ewarnn() 151 | { 152 | if ! yesno "${EINFO_QUIET}"; then 153 | _eprint "${WARN}" "$@" >&2 154 | esyslog "daemon.warning" "${0##*/}" "$@" 155 | fi 156 | } 157 | 158 | # 159 | # This behaves as the eend function does, except that the given diagnostic 160 | # message shall be presented as a warning rather than an error. 161 | # 162 | ewend() 163 | { 164 | : "${genfun_caller:=ewend}" 165 | _eend ewarn "$@" 166 | } 167 | 168 | # 169 | # Determines whether the kernel cmdline contains the specified parameter as a 170 | # component of a comma-separated list specified in the format of gentoo=. 171 | # 172 | get_bootparam() 173 | ( 174 | # Gentoo cmdline parameters are comma-delimited, so a search 175 | # string containing a comma must not be allowed to match. 176 | # Similarly, the empty string must not be allowed to match. 177 | case $1 in ''|*,*) return 1 ;; esac 178 | 179 | # Reset the value of IFS because there is no telling what it may be. 180 | IFS=$(printf ' \n\t') 181 | 182 | if [ "${TEST_GENFUNCS}" = 1 ]; then 183 | read -r cmdline 184 | else 185 | read -r cmdline < /proc/cmdline 186 | fi || return 187 | 188 | # Disable pathname expansion. The definition of this function 189 | # is a compound command that incurs a subshell. Therefore, the 190 | # prior state of the option does not need to be recalled. 191 | set -f 192 | for opt in ${cmdline}; do 193 | gentoo_opt=${opt#gentoo=} 194 | if [ "${opt}" != "${gentoo_opt}" ]; then 195 | case ,${gentoo_opt}, in 196 | *,"$1",*) return 0 197 | esac 198 | fi 199 | done 200 | return 1 201 | ) 202 | 203 | # 204 | # Takes the first parameter as a reference file/directory then determines 205 | # whether any of the following parameters refer to newer files/directories. 206 | # 207 | # The test utility is required to support the -nt primary, per POSIX-1.2024. 208 | # However, measures are in place to to achieve compatibility with shells that 209 | # implement the primary without yet fully adhering to the specification. 210 | # 211 | is_older_than() 212 | { 213 | local path ref 214 | 215 | if [ "$#" -eq 0 ]; then 216 | warn "is_older_than: too few arguments (got $#, expected at least 1)" 217 | return 1 218 | elif [ -e "$1" ]; then 219 | ref=$1 220 | else 221 | ref= 222 | fi 223 | shift 224 | for path; do 225 | # The first branch addresses a conformance issue whereby 226 | # [ existent -nt nonexistent ] is incorrectly false. As of 227 | # August 2024, busybox ash, dash, FreeBSD sh and NetBSD sh are 228 | # known to be non-conforming in this respect. 229 | if [ ! "${ref}" ] && [ -e "${path}" ]; then 230 | return 231 | elif [ "${path}" -nt "${ref}" ]; then 232 | return 233 | elif [ -d "${path}" ] && is_older_than "${ref}" "${path}"/*; then 234 | return 235 | fi 236 | done 237 | false 238 | } 239 | 240 | # 241 | # Declare the vebegin, veerror, veindent, veinfo, veinfon, veoutdent and vewarn 242 | # functions. These differ from their non-v-prefixed counterparts in that they 243 | # only have an effect where EINFO_VERBOSE is true. 244 | # 245 | for _ in vebegin veerror veindent veinfo veinfon veoutdent vewarn; do 246 | eval " 247 | $_ () 248 | { 249 | if yesno \"\${EINFO_VERBOSE}\"; then 250 | ${_#v} \"\$@\" 251 | fi 252 | } 253 | " 254 | done 255 | 256 | veend() 257 | { 258 | if yesno "${EINFO_VERBOSE}"; then 259 | genfun_caller=veend 260 | eend "$@" 261 | elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then 262 | _warn_for_args veend "$1" 263 | false 264 | else 265 | return "$1" 266 | fi 267 | } 268 | 269 | vewend() 270 | { 271 | if yesno "${EINFO_VERBOSE}"; then 272 | genfun_caller=vewend 273 | ewend "$@" 274 | elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then 275 | _warn_for_args vewend "$1" 276 | false 277 | else 278 | return "$1" 279 | fi 280 | } 281 | 282 | # 283 | # Determines whether the first parameter is truthy. The values taken to be true 284 | # are "yes", "true", "on" and "1", whereas their opposites are taken to be 285 | # false. The empty string is also taken to be false. All pattern matching is 286 | # performed case-insensitively. 287 | # 288 | yesno() 289 | { 290 | local arg 291 | 292 | if [ "$#" -eq 0 ]; then 293 | warn "yesno: too few arguments (got $#, expected 1)" 294 | return 1 295 | fi 296 | arg=$1 297 | for _ in 1 2; do 298 | case ${arg} in 299 | [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0|'') 300 | return 1 301 | ;; 302 | [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 303 | return 0 304 | esac 305 | if [ "$_" -ne 1 ] || ! is_identifier "$1"; then 306 | break 307 | else 308 | # The value appears to be a legal variable name. Treat 309 | # it as a name reference and try again, once only. 310 | eval "arg=\$$1" 311 | fi 312 | done 313 | _warn_for_args yesno "$@" 314 | false 315 | } 316 | 317 | #------------------------------------------------------------------------------# 318 | 319 | # 320 | # Called by eend, ewend, veend and vewend. See the definition of eend for an 321 | # overall description of its purpose. 322 | # 323 | _eend() 324 | { 325 | local col efunc msg retval 326 | 327 | efunc=$1 328 | shift 329 | if [ "$#" -eq 0 ]; then 330 | retval=0 331 | elif ! is_int "$1" || [ "$1" -lt 0 ]; then 332 | _warn_for_args "${genfun_caller}" "$1" 333 | retval=1 334 | msg= 335 | else 336 | retval=$1 337 | shift 338 | msg=$* 339 | fi 340 | 341 | genfun_caller= 342 | 343 | if [ "${retval}" -ne 0 ]; then 344 | # If a message was given, print it with the specified function. 345 | if _is_visible "${msg}"; then 346 | "${efunc}" "${msg}" 347 | fi 348 | # Generate an indicator for ebegin's unsuccessful conclusion. 349 | # shellcheck disable=2154 350 | if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then 351 | msg="[ !! ]" 352 | else 353 | msg="${BRACKET}[ ${BAD}!!${BRACKET} ]${NORMAL}" 354 | fi 355 | elif yesno "${EINFO_QUIET}"; then 356 | return "${retval}" 357 | else 358 | # Generate an indicator for ebegin's successful conclusion. 359 | # shellcheck disable=2154 360 | if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then 361 | msg="[ ok ]" 362 | else 363 | msg="${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}" 364 | fi 365 | fi 366 | 367 | if [ "${genfun_tty}" -eq 2 ]; then 368 | # Save the cursor position with DECSC, move it up by one line 369 | # with CUU, position it horizontally with CHA, print the 370 | # indicator, then restore the cursor position with DECRC. 371 | # shellcheck disable=2154 372 | col=$(( genfun_cols > 6 ? genfun_cols - 6 : 1 )) 373 | printf '\0337\033[1A\033[%dG %s\0338' "$(( col + genfun_offset ))" "${msg}" 374 | else 375 | # The standard output refers either to an insufficiently capable 376 | # terminal or to something other than a terminal. Print the 377 | # indicator, using characters to indent to the extent 378 | # that the last character falls on the 80th column. This hinges 379 | # on the fair assumption that a newline was already printed. 380 | printf '%80s\n' "${msg}" 381 | fi 382 | 383 | return "${retval}" 384 | } 385 | 386 | # 387 | # Determines whether the given string is newline-terminated. 388 | # 389 | _ends_with_newline() 390 | { 391 | test "${genfun_newline}" \ 392 | && ! case $1 in *"${genfun_newline}") false ;; esac 393 | } 394 | 395 | # 396 | # Called by ebegin, eerrorn, einfon, and ewarnn. 397 | # 398 | _eprint() 399 | { 400 | local color 401 | 402 | color=$1 403 | shift 404 | if [ -t 1 ]; then 405 | printf ' %s*%s %s%s' "${color}" "${NORMAL}" "${genfun_indent}" "$*" 406 | else 407 | printf ' * %s%s' "${genfun_indent}" "$*" 408 | fi 409 | } 410 | 411 | # 412 | # Called by eindent, eoutdent, veindent and veoutdent. It is here that the 413 | # variable containing the horizontal whitespace is updated. 414 | # 415 | _esetdent() 416 | { 417 | if [ "$1" -lt 0 ]; then 418 | set -- 0 419 | fi 420 | genfun_indent=$(printf "%${1}s" '') 421 | } 422 | 423 | # 424 | # Tries to determine whether the terminal supports ECMA-48 SGR color sequences. 425 | # 426 | _has_color_terminal() 427 | { 428 | local colors 429 | 430 | # The tput(1) invocation is not portable, though ncurses suffices. In 431 | # this day and age, it is exceedingly unlikely that it will be needed. 432 | if _has_dumb_terminal; then 433 | false 434 | elif colors=$(tput colors 2>/dev/null) && is_int "${colors}"; then 435 | test "${colors}" -gt 0 436 | else 437 | true 438 | fi 439 | } 440 | 441 | # 442 | # Determines whether the first parameter contains any visible characters. 443 | # 444 | _is_visible() 445 | { 446 | ! case $1 in *[[:graph:]]*) false ;; esac 447 | } 448 | 449 | #------------------------------------------------------------------------------# 450 | 451 | # Determine whether the use of color is to be wilfully avoided. 452 | if [ "${NO_COLOR}" ]; then 453 | # See https://no-color.org/. 454 | RC_NOCOLOR=yes 455 | else 456 | for _; do 457 | case $_ in 458 | --nocolor|--nocolour|-C) 459 | warn "the $_ option is deprecated by gentoo-functions; please set NO_COLOR=1 instead" 460 | RC_NOCOLOR=yes 461 | break 462 | esac 463 | done 464 | fi 465 | 466 | if ! _has_color_terminal || yesno "${RC_NOCOLOR}"; then 467 | unset -v BAD BRACKET GOOD HILITE NORMAL WARN 468 | else 469 | # Define some ECMA-48 SGR sequences for color support. These variables 470 | # are public, in so far as users of the library may be expanding them. 471 | # Conveniently, these sequences are documented by console_codes(4). 472 | BAD=$(printf '\033[31;01m') 473 | BRACKET=$(printf '\033[34;01m') 474 | GOOD=$(printf '\033[32;01m') 475 | # shellcheck disable=2034 476 | HILITE=$(printf '\033[36;01m') 477 | NORMAL=$(printf '\033[0m') 478 | WARN=$(printf '\033[33;01m') 479 | fi 480 | 481 | # In Emacs, M-x term opens an "eterm-color" terminal, whose implementation of 482 | # the CHA (ECMA-48 CSI) sequence suffers from an off-by-one error. 483 | if [ "${INSIDE_EMACS}" ] && [ "${TERM}" = "eterm-color" ]; then 484 | genfun_offset=-1 485 | else 486 | genfun_offset=0 487 | fi 488 | 489 | # Assign the LF ('\n') character for later expansion. POSIX-1.2024 permits $'\n' 490 | # but it may take years for it to be commonly implemented. 491 | genfun_newline=' 492 | ' 493 | 494 | # shellcheck disable=2034 495 | RC_GOT_FUNCTIONS=yes 496 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /functions.sh: -------------------------------------------------------------------------------- 1 | # Copyright 1999-2024 Gentoo Authors 2 | # Distributed under the terms of the GNU General Public License v2 3 | # shellcheck shell=sh disable=2153,2209,3013,3043 4 | 5 | # This file contains a series of function declarations followed by some 6 | # initialisation code. Functions intended for internal use shall be prefixed 7 | # with an and shall not be considered as being a part of the public 8 | # API. With the exception of those declared by the local builtin, all variables 9 | # intended for internal use shall be prefixed with "genfun_" to indicate so, 10 | # and to reduce the probability of name space conflicts. 11 | 12 | # The functions shall be compatible with the POSIX-1.2018 Shell and Utilities 13 | # (XCU), except where otherwise noted and with the exception that the use of 14 | # the local utility is permitted, despite the results of its invocation being 15 | # formally unspecified. Should any of the errexit, pipefail or nounset options 16 | # be enabled in the shell, the behaviour of gentoo-functions as a whole shall 17 | # be unspecified. 18 | 19 | # The following variables affect initialisation and/or function behaviour. 20 | 21 | # BASH : whether bash-specific features may be employed 22 | # BASH_VERSINFO : whether bash-specific features may be employed 23 | # BASHPID : may be used by _update_columns() and _update_pid() 24 | # COLUMNS : may be used by _update_columns() to get the column count 25 | # EPOCHREALTIME : potentially used by _update_time() to get the time 26 | # GENFUN_MODULES : which of the optional function collections must be sourced 27 | # IFS : warn() operands are joined by its first character 28 | # INVOCATION_ID : used by from_unit() 29 | # KSH_VERSION : used to detect ksh93, which is currently unsupported 30 | # PORTAGE_BIN_PATH : used by from_portage() 31 | # POSIXLY_CORRECT : if unset/empty, quote_args() may emit dollar-single-quotes 32 | # RC_OPENRC_PID : used by from_runscript() 33 | # SENTINEL : can define a value separating two distinct argument lists 34 | # SYSTEMD_EXEC_PID : used by from_unit() 35 | # TERM : used to detect dumb terminals 36 | # YASH_VERSION : for detecting yash before checking for incompatible options 37 | 38 | #------------------------------------------------------------------------------# 39 | 40 | # 41 | # Prints a diagnostic message prefixed with the basename of the running script. 42 | # 43 | warn() 44 | { 45 | printf '%s: %s\n' "${0##*/}" "$*" >&2 46 | } 47 | 48 | case ${KSH_VERSION} in 'Version AJM 93'*) 49 | # The ksh93 shell has a typeset builtin but no local builtin. 50 | warn "gentoo-functions does not currently support ksh93" 51 | return 1 52 | esac 53 | 54 | # shellcheck disable=3062 55 | if [ "${YASH_VERSION}" ] && [ -o posixlycorrect ]; then 56 | # The yash shell disables the local builtin in its POSIXly-correct mode. 57 | warn "gentoo-functions does not support yash in posixlycorrect mode" 58 | return 1 59 | fi 60 | 61 | case $- in *[eu]*) 62 | # https://lists.gnu.org/archive/html/help-bash/2020-04/msg00049.html 63 | warn "gentoo-functions supports neither the errexit option nor the nounset option; unexpected behaviour may ensue" 64 | esac 65 | 66 | # 67 | # Considers the first parameter as a reference to a variable by name and 68 | # assigns the second parameter as its value. If the first parameter is found 69 | # not to be a legal identifier, no assignment shall occur and the return value 70 | # shall be greater than 0. 71 | # 72 | assign() 73 | { 74 | if [ "$#" -ne 2 ]; then 75 | warn "assign: wrong number of arguments (got $#, expected 2)" 76 | false 77 | elif ! is_identifier "$1"; then 78 | _warn_for_args assign "$@" 79 | false 80 | else 81 | eval "$1=\$2" 82 | fi 83 | } 84 | 85 | # 86 | # A safe wrapper for the cd builtin. To run cd "$dir" is problematic because: 87 | # 88 | # - it may consider its operand as an option 89 | # - it will search CDPATH for an operand not beginning with ./, ../ or / 90 | # - it will switch to OLDPWD if the operand is - 91 | # - cdable_vars causes bash to treat the operand as a potential variable name 92 | # 93 | chdir() 94 | { 95 | if [ "${BASH}" ]; then 96 | # shellcheck disable=3044 97 | shopt -u cdable_vars 98 | fi 99 | # shellcheck disable=1007,2164 100 | case $1 in 101 | '') 102 | printf >&2 'chdir: null directory\n' 103 | false 104 | ;; 105 | /*) 106 | cd -P "$1" 107 | ;; 108 | *) 109 | CDPATH= cd -P "./$1" 110 | esac 111 | } 112 | 113 | # 114 | # Considers the first parameter as a string comprising zero or more 115 | # whitespace-separated words then determines whether all of the remaining 116 | # parameters can be found within the resulting list in their capacity as 117 | # discrete words. If they cannot be, or if fewer than two parameters were given, 118 | # the return value shall be 1. Of the words to be searched for, any which are 119 | # empty or which contain whitespace characters shall be deemed unfindable. 120 | # 121 | contains_all() 122 | { 123 | local arg haystack 124 | 125 | [ "$#" -gt 1 ] || return 126 | haystack=" $1 " 127 | shift 128 | for arg; do 129 | case ${arg} in 130 | ''|*[[:space:]]*) 131 | return 1 132 | esac 133 | case ${haystack} in 134 | *" ${arg} "*) 135 | ;; 136 | *) 137 | return 1 138 | esac 139 | done 140 | } 141 | 142 | # 143 | # Considers the first parameter as a string comprising zero or more 144 | # whitespace-separated words then determines whether any of the remaining 145 | # parameters can be found within the resulting list in their capacity as 146 | # discrete words. If none can be, or if no parameters were given, the return 147 | # value shall be greater than 0. Of the words to be searched for, any which are 148 | # empty or which contain whitespace characters shall be disregarded. 149 | # 150 | contains_any() 151 | { 152 | local arg haystack 153 | 154 | [ "$#" -gt 0 ] || return 155 | haystack=" $1 " 156 | shift 157 | for arg; do 158 | case ${arg} in 159 | ''|*[[:space:]]*) 160 | continue 161 | esac 162 | case ${haystack} in 163 | *" ${arg} "*) 164 | return 0 165 | esac 166 | done 167 | false 168 | } 169 | 170 | # 171 | # Considers the first parameter as a reference to a variable by name and 172 | # attempts to retrieve its presently assigned value. If only one parameter is 173 | # specified, the retrieved value shall be printed to the standard output. If a 174 | # second parameter is also specified, it shall be be taken as the name of a 175 | # variable to which the retrieved value shall be assigned. If any parameter is 176 | # found not to be a legal identifier, or if the variable referenced by the 177 | # first parameter is unset, the return value shall be greater than 0. 178 | # 179 | deref() 180 | { 181 | if [ "$#" -eq 0 ] || [ "$#" -gt 2 ]; then 182 | warn "deref: wrong number of arguments (got $#, expected between 1 and 2)" 183 | elif ! trueof_all is_identifier -- "$@"; then 184 | _warn_for_args deref "$@" 185 | false 186 | elif ! eval "test \"\${$1+set}\""; then 187 | false 188 | elif [ "$#" -eq 1 ]; then 189 | eval "printf '%s\\n' \"\$$1\"" 190 | else 191 | eval "$2=\$$1" 192 | fi 193 | } 194 | 195 | # 196 | # Determines whether the current shell is a subprocess of portage. 197 | # 198 | from_portage() 199 | { 200 | test "${PORTAGE_BIN_PATH}" 201 | } 202 | 203 | # 204 | # Determines whether the current shell is executing an OpenRC runscript, or is 205 | # a subprocess of one. 206 | # 207 | from_runscript() 208 | { 209 | has_openrc && test "${RC_OPENRC_PID}" 210 | } 211 | 212 | # 213 | # Determines whether the current shell is a subprocess of a systemd unit that 214 | # handles a service, socket, mount point or swap device, per systemd.exec(5). 215 | # 216 | from_unit() 217 | { 218 | has_systemd && test "${SYSTEMD_EXEC_PID}" && test "${INVOCATION_ID}" 219 | } 220 | 221 | # 222 | # Determines whether OpenRC appears to be operational as a service manager in 223 | # the context of the present root filesystem namespace. 224 | # 225 | has_openrc() 226 | { 227 | test -d /run/openrc 228 | } 229 | 230 | # 231 | # Determines whether systemd appears to be operational as a service manager in 232 | # the context of the present root filesystem namespace. 233 | # 234 | has_systemd() 235 | { 236 | test -d /run/systemd 237 | } 238 | 239 | # 240 | # Prints a horizontal rule. If specified, the first parameter shall be taken as 241 | # a string whose first character is to be repeated in the course of composing 242 | # the rule. Otherwise, or if specified as the empty string, it shall default to 243 | # the . If specified, the second parameter shall define the length 244 | # of the rule in characters. Otherwise, it shall default to the width of the 245 | # terminal if such can be determined, or 80 if it cannot be. 246 | # 247 | hr() 248 | { 249 | local c hr i length 250 | 251 | if [ "$#" -ge 2 ] && is_int "$2"; then 252 | length=$2 253 | elif _update_tty_level <&1; [ "${genfun_tty}" -eq 2 ]; then 254 | length=${genfun_cols} 255 | else 256 | length=80 257 | fi 258 | c=${1--} 259 | c=${c%"${c#?}"} 260 | hr= 261 | i=0 262 | while [ "$(( i += 16 ))" -le "${length}" ]; do 263 | hr=${hr}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c}${c} 264 | done 265 | i=${#hr} 266 | while [ "$(( i += 1 ))" -le "${length}" ]; do 267 | hr=${hr}${c} 268 | done 269 | printf '%s\n' "${hr}" 270 | } 271 | 272 | # 273 | # Determines whether the first parameter is a valid identifier (variable name). 274 | # 275 | is_identifier() 276 | { 277 | case $1 in 278 | ''|_|[0123456789]*|*[!_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]*) 279 | false 280 | esac 281 | } 282 | 283 | # 284 | # Determines whether the first parameter is a valid integer. A leading 285 | # shall be permitted. Thereafter, leading zeroes shall not be 286 | # permitted because the string might later be considered to be octal in an 287 | # arithmetic context, causing the shell to exit if the number be invalid. 288 | # 289 | is_int() 290 | { 291 | set -- "${1#-}" 292 | case $1 in 293 | ''|*[!0123456789]*) 294 | false 295 | ;; 296 | 0) 297 | true 298 | ;; 299 | *) 300 | test "$1" = "${1#0}" 301 | esac 302 | } 303 | 304 | # 305 | # Determines whether the first parameter matches any of the parameters that 306 | # follow it. 307 | # 308 | is_anyof() 309 | { 310 | local arg needle 311 | 312 | if [ "$#" -eq 0 ]; then 313 | warn "is_anyof: too few arguments (got $#, expected at least 1)" 314 | else 315 | needle=$1 316 | shift 317 | for arg; do 318 | if [ "${arg}" = "${needle}" ]; then 319 | return 320 | fi 321 | done 322 | fi 323 | false 324 | } 325 | 326 | # 327 | # Considers one or more pathnames and prints the one having the newest 328 | # modification time. If at least one parameter is provided, all parameters 329 | # shall be considered as pathnames to be compared to one another. Otherwise, 330 | # the pathnames to be compared shall be read from the standard input as 331 | # null-terminated records. In the case that two or more pathnames are 332 | # candidates, whichever was first specified shall take precedence over the 333 | # other. If no pathnames are given, or those specified do not exist, the return 334 | # value shall be greater than 0. 335 | # 336 | # Pathnames containing characters shall be handled correctly if 337 | # conveyed as positional parameters. Otherwise, the behaviour for such 338 | # pathnames is unspecified. Users of the function are duly expected to refrain 339 | # from conveying such pathnames for consumption from the standard input; for 340 | # example, by specifying a predicate of ! -path $'*\n*' to the find utility. 341 | # This constraint is expected to be eliminated by a future amendment to the 342 | # function, once support for read -d becomes sufficiently widespread. 343 | # 344 | # The test utility is required to support the -nt primary, per POSIX-1.2024. 345 | # However, measures are in place to to achieve compatibility with shells that 346 | # implement the primary without yet fully adhering to the specification. 347 | # 348 | newest() 349 | { 350 | local path newest 351 | 352 | newest= 353 | if [ "$#" -gt 0 ]; then 354 | for path; do 355 | # The tests within curly braces address a conformance 356 | # issue whereby [ existent -nt nonexistent ] is 357 | # incorrectly false. As of August 2024, busybox ash, 358 | # dash, FreeBSD sh and NetBSD sh are known to be 359 | # non-conforming in this respect. 360 | if { [ ! "${newest}" ] && [ -e "${path}" ]; } || [ "${path}" -nt "${newest}" ]; then 361 | newest=${path} 362 | fi 363 | done 364 | test "${newest}" && printf '%s\n' "${newest}" 365 | else 366 | # Support for read -d '' is not yet sufficiently widespread. 367 | tr '\0' '\n' | 368 | { 369 | while IFS= read -r path; do 370 | if { [ ! "${newest}" ] && [ -e "${path}" ]; } || [ "${path}" -nt "${newest}" ]; then 371 | newest=${path} 372 | fi 373 | done 374 | test "${newest}" && printf '%s\n' "${newest}" 375 | } 376 | fi 377 | } 378 | 379 | # 380 | # Tries to determine the number of available processors. Falls back to trying to 381 | # determine the number of online processors in a way that is somewhat portable. 382 | # 383 | get_nprocs() 384 | { 385 | if nproc 2>/dev/null; then 386 | # The nproc(1) utility is provided by GNU coreutils. It has the 387 | # advantage of acknowledging the effect of sched_setaffinity(2). 388 | true 389 | elif getconf _NPROCESSORS_ONLN 2>/dev/null; then 390 | # This constant is standard as of POSIX-1.2024 and was already 391 | # supported by glibc, musl-utils, macOS, FreeBSD, NetBSD and 392 | # OpenBSD. 393 | true 394 | else 395 | warn "get_nprocs: failed to determine the number of processors" 396 | false 397 | fi 398 | } 399 | 400 | # 401 | # Considers one or more pathnames and prints the one having the oldest 402 | # modification time. If at least one parameter is provided, all parameters 403 | # shall be considered as pathnames to be compared to one another. Otherwise, 404 | # the pathnames to be compared shall be read from the standard input as 405 | # null-terminated records. In the case that two or more pathnames are 406 | # candidates, whichever was first specified shall take precedence over the 407 | # other. If no pathnames are given, or those specified do not exist, the return 408 | # value shall be greater than 0. 409 | # 410 | # Pathnames containing characters shall be handled correctly if 411 | # conveyed as positional parameters. Otherwise, the behaviour for such 412 | # pathnames is unspecified. Users of the function are duly expected to refrain 413 | # from conveying such pathnames for consumption from the standard input; for 414 | # example, by specifying a predicate of ! -path $'*\n*' to the find utility. 415 | # This constraint is expected to be eliminated by a future amendment to the 416 | # function, once support for read -d becomes sufficiently widespread. 417 | # 418 | # The test utility is required to support the -ot primary, per POSIX-1.2024. 419 | # 420 | oldest() 421 | { 422 | local path oldest 423 | 424 | oldest= 425 | if [ "$#" -gt 0 ]; then 426 | for path; do 427 | # The specification has [ nonexistent -ot existent ] as 428 | # being true. Such is a nuisance in this case but the 429 | # preceding tests suffice as a workaround. 430 | if [ ! -e "${path}" ]; then 431 | continue 432 | elif [ ! "${oldest}" ] || [ "${path}" -ot "${oldest}" ]; then 433 | oldest=${path} 434 | fi 435 | done 436 | test "${oldest}" && printf '%s\n' "${oldest}" 437 | else 438 | # Support for read -d '' is not yet sufficiently widespread. 439 | tr '\0' '\n' | 440 | { 441 | while IFS= read -r path; do 442 | if [ ! -e "${path}" ]; then 443 | continue 444 | elif [ ! "${oldest}" ] || [ "${path}" -ot "${oldest}" ]; then 445 | oldest=${path} 446 | fi 447 | done 448 | test "${oldest}" && printf '%s\n' "${oldest}" 449 | } 450 | fi 451 | } 452 | 453 | # 454 | # Executes a simple command in parallel. At least two parameters are expected. 455 | # The first parameter shall be taken as the maximum number of jobs to run 456 | # concurrently. If specified as less than or equal to 0, the number shall be 457 | # determined by calling the get_nprocs function. The second parameter shall be 458 | # taken as a command name. The remaining parameters shall be conveyed to the 459 | # specified command, one at a time. Should at least one command fail, the 460 | # return value shall be greater than 0. 461 | # 462 | parallel_run() 463 | { 464 | local arg cmd i statedir w workers 465 | 466 | if [ "$#" -lt 3 ]; then 467 | warn "parallel_run: too few arguments (got $#, expected at least 3)" 468 | return 1 469 | elif ! is_int "$1"; then 470 | _warn_for_args parallel_run "$1" 471 | return 1 472 | elif [ "$1" -le 0 ] && ! workers=$(get_nprocs); then 473 | return 1 474 | elif ! statedir=${TMPDIR:-/tmp}/parallel_run.$$.$(srandom); then 475 | return 1 476 | fi 477 | workers=${workers:-$1} cmd=$2 478 | shift 2 479 | w=0 480 | i=0 481 | ( 482 | while [ "$(( w += 1 ))" -le "${workers}" ]; do 483 | i=$w 484 | while [ "$i" -le "$#" ]; do 485 | eval "arg=\$${i}" 486 | if ! "${cmd}" "${arg}"; then 487 | mkdir -p -- "${statedir}" 488 | fi 489 | i=$(( i + workers )) 490 | done & 491 | done 492 | wait 493 | ) 494 | ! rmdir -- "${statedir}" 2>/dev/null 495 | } 496 | 497 | # 498 | # Prints the positional parameters in a format that may be reused as shell 499 | # input. For each considered, it shall be determined whether its value contains 500 | # any bytes that are either outside the scope of the US-ASCII character set or 501 | # which are considered as non-printable. If no such bytes are found, the value 502 | # shall have each instance of be replaced by 503 | # before being enclosed by a pair of 504 | # characters. However, as a special case, a value consisting of a 505 | # single shall be replaced by . 506 | # 507 | # If any such bytes are found, the value shall instead be requoted in a manner 508 | # that conforms with section 2.2.4 of the Shell Command Language, wherein the 509 | # the use of dollar-single-quotes sequences is described. Such sequences are 510 | # standard as of POSIX-1.2024. However, as of August 2024, many implementations 511 | # lack support for this feature. So as to mitigate this state of affairs, the 512 | # use of dollar-single-quotes may be suppressed by setting POSIXLY_CORRECT as a 513 | # non-empty string. 514 | # 515 | quote_args() 516 | { 517 | # Call into a bash-optimised implementation where appropriate. 518 | # shellcheck disable=3028 519 | if [ ! "${POSIXLY_CORRECT}" ] && [ "${BASH_VERSINFO-0}" -ge 5 ]; then 520 | _quote_args_bash "$@" 521 | return 522 | fi 523 | LC_ALL=C awk -v q=\' -f - -- "$@" <<-'EOF' 524 | function init_table() { 525 | # Iterate over ranges \001-\037 and \177-\377. 526 | for (i = 1; i <= 255; i += (i == 31 ? 96 : 1)) { 527 | char = sprintf("%c", i) 528 | seq_by[char] = sprintf("%03o", i) 529 | } 530 | seq_by["\007"] = "a" 531 | seq_by["\010"] = "b" 532 | seq_by["\011"] = "t" 533 | seq_by["\012"] = "n" 534 | seq_by["\013"] = "v" 535 | seq_by["\014"] = "f" 536 | seq_by["\015"] = "r" 537 | seq_by["\033"] = "e" 538 | seq_by["\047"] = "'" 539 | seq_by["\134"] = "\\" 540 | } 541 | BEGIN { 542 | issue = length(ENVIRON["POSIXLY_CORRECT"]) ? 7 : 8; 543 | argc = ARGC 544 | ARGC = 1 545 | for (arg_idx = 1; arg_idx < argc; arg_idx++) { 546 | arg = ARGV[arg_idx] 547 | if (arg == q) { 548 | word = "\\" q 549 | } else if (issue < 8 || arg !~ /[\001-\037\177-\377]/) { 550 | gsub(q, q "\\" q q, arg) 551 | word = q arg q 552 | } else { 553 | # Use $'' quoting per POSIX-1.2024. 554 | if (! ("\001" in seq_by)) { 555 | init_table() 556 | } 557 | word = "$'" 558 | for (i = 1; i <= length(arg); i++) { 559 | char = substr(arg, i, 1) 560 | if (char in seq_by) { 561 | word = word "\\" seq_by[char] 562 | } else { 563 | word = word char 564 | } 565 | } 566 | word = word q 567 | } 568 | line = line word 569 | if (arg_idx < argc - 1) { 570 | line = line " " 571 | } 572 | } 573 | print line 574 | } 575 | EOF 576 | } 577 | 578 | # 579 | # Generates a random number between 0 and 2147483647 (2^31-1) with the 580 | # assistance of the kernel CSPRNG. Upon success, the number shall be printed to 581 | # the standard output along with a trailing . Otherwise, the return 582 | # value shall be greater than 0. 583 | # 584 | # shellcheck disable=3028 585 | if [ "${BASH_VERSINFO-0}" -ge 5 ] && [ "${SRANDOM}" != "${SRANDOM}" ]; then 586 | srandom() 587 | { 588 | printf '%d\n' "$(( SRANDOM >> 1 ))" 589 | } 590 | elif [ -c /dev/urandom ]; then 591 | unset -v genfun_entropy 592 | 593 | srandom() 594 | { 595 | local hex slice 596 | 597 | if [ "${#genfun_entropy}" -lt 8 ]; then 598 | # Not enough entropy is left in the pool. 599 | _collect_entropy 600 | elif ! _update_pid; then 601 | # Fork detection is unavailable. 602 | _collect_entropy 603 | elif ! eval "test \"\${genfun_pool_${genfun_pid}+set}\""; then 604 | # A newly forked shell has been detected. 605 | _collect_entropy && 606 | eval "genfun_pool_${genfun_pid}=1" 607 | fi || return 608 | 609 | # Consume 8 hex digits (32 bits) from the pool. 610 | slice=${genfun_entropy%????????} 611 | hex=${genfun_entropy#"$slice"} 612 | genfun_entropy=${slice} 613 | 614 | # Clamp to the desired range (0x7FFFFFFF at most). 615 | case ${hex} in 616 | 8*) hex=0${hex#?} ;; 617 | 9*) hex=1${hex#?} ;; 618 | a*) hex=2${hex#?} ;; 619 | b*) hex=3${hex#?} ;; 620 | c*) hex=4${hex#?} ;; 621 | d*) hex=5${hex#?} ;; 622 | e*) hex=6${hex#?} ;; 623 | f*) hex=7${hex#?} 624 | esac 625 | 626 | # Print as decimal. 627 | printf '%d\n' "0x${hex}" 628 | } 629 | else 630 | srandom() { 631 | warn "srandom: /dev/urandom doesn't exist as a character device" 632 | return 1 633 | } 634 | fi 635 | 636 | # 637 | # Trims leading and trailing whitespace from one or more lines. If at least one 638 | # parameter is provided, each positional parameter shall be considered as a line 639 | # to be processed. Otherwise, the lines to be processed shall be read from the 640 | # standard input. The trimmed lines shall be printed to the standard output. 641 | # 642 | trim() 643 | { 644 | local arg 645 | 646 | if [ "$#" -gt 0 ] && [ "${BASH}" ]; then 647 | for arg; do 648 | eval '[[ ${arg} =~ ^[[:space:]]+ ]] && arg=${arg:${#BASH_REMATCH}}' 649 | eval '[[ ${arg} =~ [[:space:]]+$ ]] && arg=${arg:0:${#arg} - ${#BASH_REMATCH}}' 650 | printf '%s\n' "${arg}" 651 | done 652 | else 653 | if [ "$#" -gt 0 ]; then 654 | printf '%s\n' "$@" 655 | else 656 | cat 657 | fi | sed -e 's/^[[:space:]]\{1,\}//' -e 's/[[:space:]]\{1,\}$//' 658 | fi 659 | } 660 | 661 | # 662 | # Considers the parameters up to - but not including - a sentinel value as the 663 | # words comprising a simple command then determines whether said command 664 | # succeeds for all of the remaining parameters, passing them one at a time. If 665 | # the SENTINEL variable is set, it shall be taken as the value of the sentinel. 666 | # Otherwise, the value of the sentinel shall be defined as 667 | # . If the composed command is empty, the sentinel value is not 668 | # encountered or there are no parameters following the sentinel, the return 669 | # value shall be greater than 0. 670 | # 671 | trueof_all() 672 | { 673 | local arg arg_idx i j 674 | 675 | arg_idx=0 676 | i=0 677 | j=0 678 | for arg; do 679 | if [ "$(( arg_idx += 1 ))" -eq 1 ]; then 680 | set -- 681 | fi 682 | if [ "$i" -gt 1 ]; then 683 | "$@" "${arg}" || return 684 | j=${arg_idx} 685 | elif [ "${arg}" = "${SENTINEL-"--"}" ]; then 686 | i=${arg_idx} 687 | else 688 | set -- "$@" "${arg}" 689 | fi 690 | done 691 | test "$i" -gt 1 && test "$j" -gt "$i" 692 | } 693 | 694 | # 695 | # Considers the parameters up to - but not including - a sentinel value as the 696 | # words comprising a simple command then determines whether said command 697 | # succeeds for at least one of the remaining parameters, passing them one at a 698 | # time. If the SENTINEL variable is set, it shall be taken as the value of the 699 | # sentinel. Otherwise, the value of the sentinel shall be defined as 700 | # . If the composed command is empty, the sentinel 701 | # value is not encountered or there are no parameters following the sentinel, 702 | # the return value shall be greater than 0. 703 | # 704 | trueof_any() 705 | { 706 | local arg arg_idx i 707 | 708 | arg_idx=0 709 | i=0 710 | for arg; do 711 | if [ "$(( arg_idx += 1 ))" -eq 1 ]; then 712 | set -- 713 | fi 714 | if [ "$i" -gt 1 ]; then 715 | "$@" "${arg}" && return 716 | elif [ "${arg}" = "${SENTINEL-"--"}" ]; then 717 | i=${arg_idx} 718 | else 719 | set -- "$@" "${arg}" 720 | fi 721 | done 722 | false 723 | } 724 | 725 | # 726 | # Considers the first parameter as a command name before trying to locate it as 727 | # a regular file. If not specified as an absolute pathname, a PATH search shall 728 | # be performed in accordance with the Environment Variables section of the Base 729 | # Definitions. If a file is found, its path shall be printed. Otherwise, the 730 | # return value shall be 1. If the -x option is specified then the file must 731 | # also be executable by the present user in order to be matched. This function 732 | # serves as an alternative to type -P in bash. It is useful for determining the 733 | # existence and location of an external utility without potentially matching 734 | # against aliases, builtins and functions (as command -v can). 735 | # 736 | whenceforth() 737 | ( 738 | local bin executable opt path prefix 739 | 740 | executable= 741 | while getopts :x opt; do 742 | case ${opt} in 743 | x) 744 | executable=1 745 | ;; 746 | '?') 747 | _warn_for_args whenceforth "-${OPTARG}" 748 | return 1 749 | esac 750 | done 751 | shift "$(( OPTIND - 1 ))" 752 | 753 | case $1 in 754 | /*) 755 | # Absolute command paths must be directly checked. 756 | test -f "$1" && test ${executable:+-x} "$1" && bin=$1 757 | ;; 758 | *) 759 | # Relative command paths must be searched for in PATH. 760 | # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 761 | case ${PATH} in 762 | ''|:) 763 | # Work around a bug in OpenBSD sh and 764 | # its ports. Where IFS has a value of 765 | # ":", splitting a word having the same 766 | # value produces no words at all. Handle 767 | # it by repeating the field terminator. 768 | path=:: 769 | ;; 770 | *:) 771 | path=${PATH}: 772 | ;; 773 | *) 774 | path=${PATH} 775 | esac 776 | IFS=: 777 | set -f 778 | for prefix in ${path}; do 779 | case ${prefix} in 780 | */) 781 | bin=${prefix}$1 782 | ;; 783 | *) 784 | bin=${prefix:-.}/$1 785 | esac 786 | test -f "${bin}" && test ${executable:+-x} "${bin}" && break 787 | done 788 | esac \ 789 | && printf '%s\n' "${bin}" 790 | ) 791 | 792 | #------------------------------------------------------------------------------# 793 | 794 | # 795 | # Collects 64 bytes worth of entropy from /dev/urandom and assigns it to the 796 | # genfun_entropy variable in the form of 128 hex digits. 797 | # 798 | _collect_entropy() { 799 | genfun_entropy=$(LC_ALL=C od -vAn -N64 -tx1 /dev/urandom | tr -d '[:space:]') 800 | test "${#genfun_entropy}" -eq 128 801 | } 802 | 803 | # 804 | # Determines whether the terminal is a dumb one. 805 | # 806 | _has_dumb_terminal() 807 | { 808 | ! case ${TERM} in *dumb*) false ;; esac 809 | } 810 | 811 | # 812 | # Potentially called by quote_args(), duly acting as a bash-optimised variant. 813 | # It leverages the ${paramater@Q} form of expansion, which is supported as of 814 | # bash 4.4. However, it is simpler just to test for 5.0 or greater in sh. 815 | # 816 | # shellcheck disable=3028 817 | if [ "${BASH_VERSINFO-0}" -ge 5 ]; then 818 | eval ' 819 | _quote_args_bash() { 820 | local IFS=" " args i 821 | 822 | (( $# > 0 )) || return 0 823 | args=("${@@Q}") 824 | for i in "${!args[@]}"; do 825 | if [[ ${args[i]} == \$* ]]; then 826 | args[i]=${args[i]//\\E/\\e} 827 | fi 828 | done 829 | printf "%s\\n" "${args[*]}" 830 | } 831 | ' 832 | fi 833 | 834 | # 835 | # Considers the first parameter as a number of centiseconds and determines 836 | # whether fewer have elapsed since the last occasion on which the function was 837 | # called, or whether the last genfun_time update resulted in integer overflow. 838 | # 839 | _should_throttle() 840 | { 841 | _update_time || return 842 | 843 | # shellcheck disable=2329 844 | _should_throttle() 845 | { 846 | _update_time || return 847 | if [ "$(( (genfun_time < 0 && genfun_last_time >= 0) || genfun_time - genfun_last_time > $1 ))" -eq 1 ] 848 | then 849 | genfun_last_time=${genfun_time} 850 | false 851 | fi 852 | 853 | } 854 | 855 | genfun_last_time=${genfun_time} 856 | false 857 | } 858 | 859 | # 860 | # Determines whether the terminal on STDIN is able to report its dimensions. 861 | # Upon success, the number of columns shall be stored in genfun_cols. 862 | # 863 | _update_columns() 864 | { 865 | # shellcheck disable=3044 866 | if [ "${BASH}" ] && shopt -q checkwinsize; then 867 | genfun_bin_true=$(whenceforth -x true) 868 | fi 869 | 870 | _update_columns() 871 | { 872 | local IFS 873 | 874 | # Two optimisations are applied. Firstly, the rate at which 875 | # updates can be performed is throttled to intervals of half a 876 | # second. Secondly, if running on bash then the COLUMNS variable 877 | # may be gauged, albeit only in situations where doing so can be 878 | # expected to work reliably. 879 | # shellcheck disable=3028 880 | if from_portage; then 881 | # Python's pty module is broken. For now, expect for 882 | # portage to have exported COLUMNS to the environment. 883 | set -- 0 "${COLUMNS}" 884 | elif _should_throttle 50; then 885 | test "${genfun_cols}" 886 | return 887 | elif [ "${genfun_bin_true}" ] && [ "$$" = "${BASHPID}" ]; then 888 | # To execute the true binary is faster than stty(1). 889 | "${genfun_bin_true}" 890 | set -- 0 "${COLUMNS}" 891 | else 892 | # This use of stty(1) is portable as of POSIX-1.2024. 893 | IFS=' ' 894 | # shellcheck disable=2046 895 | set -- $(stty size 2>/dev/null) 896 | fi 897 | [ "$#" -eq 2 ] && is_int "$2" && [ "$2" -gt 0 ] && genfun_cols=$2 898 | } 899 | 900 | _update_columns 901 | } 902 | 903 | # 904 | # Determines the PID of the current shell process. Upon success, the PID shall 905 | # be assigned to genfun_pid. Otherwise, the return value shall be greater than 906 | # 0. The obtained PID value will differ from the value of $$ under certain 907 | # circumstances, such as where a shell forks itself to create a subshell. 908 | # 909 | _update_pid() 910 | { 911 | if [ "${BASH}" ]; then 912 | _update_pid() 913 | { 914 | # shellcheck disable=3028 915 | genfun_pid=${BASHPID} 916 | } 917 | elif [ -d /proc/self/task ]; then 918 | # This method relies on the proc_pid_task(5) interface of Linux. 919 | _update_pid() 920 | { 921 | local dir tid 922 | 923 | tid= 924 | for dir in /proc/self/task/*/; do 925 | if [ "${tid}" ] || [ ! -e "${dir}" ]; then 926 | return 1 927 | else 928 | dir=${dir%/} 929 | tid=${dir##*/} 930 | fi 931 | done 932 | genfun_pid=${tid} 933 | } 934 | else 935 | _update_pid() 936 | { 937 | false 938 | } 939 | fi 940 | 941 | _update_pid 942 | } 943 | 944 | # 945 | # Determines either the number of centiseconds elapsed since the unix epoch or 946 | # the number of centiseconds that the operating system has been online, 947 | # depending on the capabilities of the shell and/or platform. Upon success, the 948 | # obtained value shall be assigned to genfun_time. Otherwise, the return value 949 | # shall be greater than 0. 950 | # 951 | _update_time() 952 | { 953 | # shellcheck disable=3028 954 | if [ "${BASH}" ] && [ "${EPOCHREALTIME}" != "${EPOCHREALTIME}" ]; then 955 | # shellcheck disable=2034,3045 956 | _update_time() 957 | { 958 | # Setting LC_NUMERIC as C ensures a radix character of 959 | # U+2E, duly affecting both EPOCHREALTIME and printf. 960 | local LC_ALL LC_NUMERIC=C cs s timeval 961 | 962 | timeval=${EPOCHREALTIME} 963 | s=${timeval%.*} 964 | printf -v cs '%.2f' ".${timeval#*.}" 965 | if [ "${cs}" = "1.00" ]; then 966 | cs=100 967 | else 968 | cs=${cs#0.} cs=${cs#0} 969 | fi 970 | genfun_time=$(( s * 100 + cs )) 971 | } 972 | elif [ -f /proc/uptime ] && [ ! "${YASH_VERSION}" ]; then 973 | # Yash is blacklisted because it dies upon integer overflow. 974 | _update_time() 975 | { 976 | local cs s 977 | 978 | IFS='. ' read -r s cs _ < /proc/uptime \ 979 | && genfun_time=$(( s * 100 + ${cs#0} )) 980 | } 981 | else 982 | _update_time() 983 | { 984 | return 2 985 | } 986 | fi 987 | 988 | _update_time 989 | } 990 | 991 | # 992 | # Grades the capability of the terminal attached to STDIN, assigning the level 993 | # to genfun_tty. If no terminal is detected, the level shall be 0. If a dumb 994 | # terminal is detected, the level shall be 1. If a smart terminal is detected, 995 | # the level shall be 2. For a terminal to be considered as smart, it must be 996 | # able to successfully report its dimensions. 997 | # 998 | _update_tty_level() 999 | { 1000 | if [ ! -t 0 ]; then 1001 | genfun_tty=0 1002 | elif _has_dumb_terminal || ! _update_columns; then 1003 | genfun_tty=1 1004 | else 1005 | genfun_tty=2 1006 | fi 1007 | } 1008 | 1009 | # 1010 | # Takes the first parameter as the path of a gentoo-functions module then 1011 | # determines whether it has been requested by attempting to match its basename 1012 | # against the any of the blank-separated words defined by the GENFUN_MODULES 1013 | # variable (not including the ".sh" suffix). 1014 | # 1015 | _want_module() 1016 | { 1017 | local basename 1018 | 1019 | basename=${1##*/} 1020 | contains_any "${GENFUN_MODULES}" "${basename%.sh}" 1021 | } 1022 | 1023 | # 1024 | # Prints a diagnostic message concerning invalid function arguments. The first 1025 | # argument shall be taken as a function identifier. The remaining arguments 1026 | # shall be safely rendered as a part of the diagnostic. 1027 | # 1028 | _warn_for_args() 1029 | { 1030 | local ident plural 1031 | 1032 | ident=$1 1033 | shift 1034 | [ "$#" -gt 1 ] && plural=s || plural= 1035 | warn "${ident}: invalid argument${plural}: $(quote_args "$@")" 1036 | } 1037 | 1038 | #------------------------------------------------------------------------------# 1039 | 1040 | # This shall be incremented by one upon any change being made to the public API. 1041 | # It was introduced by gentoo-functions-1.7 with an initial value of 1. 1042 | # shellcheck disable=2034 1043 | GENFUN_API_LEVEL=2 1044 | 1045 | # If genfun_basedir is unset, set genfun_prefix to the value of EPREFIX, as it 1046 | # was at the time of installing gentoo-functions, before setting genfun_basedir 1047 | # to the path of the directory to which this file was installed. Otherwise, 1048 | # honour its existing value so as to ease the development and testing process. 1049 | if [ ! "${genfun_basedir+set}" ]; then 1050 | genfun_prefix= 1051 | genfun_basedir=${genfun_prefix}/lib/gentoo 1052 | fi 1053 | 1054 | # The GENFUN_MODULES variable acts as a means of selecting modules, which are 1055 | # merely optional collections of functions. If unset then set it now. 1056 | if [ ! "${GENFUN_MODULES+set}" ]; then 1057 | # OpenRC provides various functions and utilities which have long had 1058 | # parallel implementations in gentoo-functions. Declare ours only if the 1059 | # shell is neither executing a runscript nor is a subprocess of one. 1060 | if ! from_runscript; then 1061 | GENFUN_MODULES="rc" 1062 | fi 1063 | # Several functions are available which overlap with functions and 1064 | # utilities provided by portage. These exist primarily to make it easier 1065 | # to test code outside of ebuilds. Declare them only if the shell is not 1066 | # a subprocess of portage. 1067 | if ! from_portage; then 1068 | GENFUN_MODULES="${GENFUN_MODULES}${GENFUN_MODULES+ }portage" 1069 | fi 1070 | fi 1071 | 1072 | # Source any modules that have been selected by the GENFUN_MODULES variable. 1073 | for _ in "${genfun_basedir}/functions"/*.sh; do 1074 | if ! test -e "$_"; then 1075 | warn "no gentoo-functions modules were found (genfun_basedir might be set incorrectly)" 1076 | false 1077 | elif _want_module "$_"; then 1078 | . "$_" 1079 | fi || return 1080 | done 1081 | -------------------------------------------------------------------------------- /test-functions: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=1007,2015,2031,2164,2317,3043 3 | 4 | # Requires mktemp(1), which is not a standard utility, but is commonly 5 | # available. The implementations provided by GNU coreutils, busybox and toybox 6 | # are known to be compatible. 7 | 8 | bailout() { 9 | printf 'Bail out! %s.\n' "$1" 10 | cleanup_tmpdir 11 | exit 1 12 | } 13 | 14 | assign_tmpdir() { 15 | global_tmpdir=$(mktemp -d) \ 16 | && chdir "${global_tmpdir}" \ 17 | || bailout "Couldn't create or change to the temp dir" 18 | } 19 | 20 | cleanup_tmpdir() { 21 | if [ "${global_tmpdir}" ]; then 22 | rm -r -- "${global_tmpdir}" 23 | fi 24 | } 25 | 26 | test_local() { 27 | set -- eq 0 28 | 29 | callback() { 30 | test_description="/bin/sh supports local" 31 | ( 32 | var=1 33 | f() { 34 | local var=2 35 | g 36 | test "${var}" = 3 || exit 37 | } 38 | g() { 39 | test "${var}" = 2 || exit 40 | var=3 41 | } 42 | f 43 | test "${var}" = 1 44 | ) 2>/dev/null 45 | } 46 | 47 | iterate_tests 2 "$@" 48 | } 49 | 50 | test_chdir() { 51 | set -- \ 52 | ge 1 '' \ 53 | ge 1 N/A \ 54 | ge 1 grandchild \ 55 | ge 1 var \ 56 | eq 0 -L \ 57 | eq 0 -p \ 58 | eq 0 -e \ 59 | eq 0 -@ \ 60 | eq 0 - \ 61 | eq 0 child 62 | 63 | if ! mkdir -p -- -L -p -e -@ - child child/grandchild; then 64 | bailout "Couldn't set up all test directories" 65 | fi 66 | 67 | callback() { 68 | local CDPATH var 69 | 70 | shift 71 | test_description="chdir $(quote_args "$@")" 72 | if [ "$BASH" ]; then 73 | # shellcheck disable=3044 74 | shopt -s cdable_vars 75 | fi 76 | CDPATH=child 77 | var=child 78 | chdir "$@" \ 79 | && test "$PWD" != "$OLDPWD" \ 80 | && cd - >/dev/null 81 | } 82 | 83 | iterate_tests 3 "$@" 84 | } 85 | 86 | test_die() { 87 | set -- \ 88 | eq 1 0 \ 89 | eq 2 2 \ 90 | eq 126 126 \ 91 | eq 255 255 92 | 93 | callback() { 94 | local retval stderr 95 | 96 | test_description="( exit $2 ); die" 97 | ( exit "$2" ) 98 | stderr=$(die "$2" 2>&1) 99 | retval=$? 100 | if [ "${stderr}" = "test-functions: $2" ]; then 101 | return "${retval}" 102 | else 103 | return 1 104 | fi 105 | } 106 | 107 | iterate_tests 3 "$@" 108 | } 109 | 110 | test_ebegin() { 111 | set -- eq 0 112 | 113 | callback() { 114 | test_description="ebegin message (expecting terminating newline)" 115 | ( 116 | _eprint() { 117 | shift 118 | _ends_with_newline "$*" 119 | } 120 | ebegin "message" 121 | ) 122 | } 123 | 124 | iterate_tests 2 "$@" 125 | } 126 | 127 | test_edo() { 128 | set -- \ 129 | eq 1 false \ 130 | eq 0 true 131 | 132 | callback() { 133 | shift 134 | test_description="edo $1" 135 | ( edo "$1" >/dev/null ) 136 | } 137 | 138 | iterate_tests 3 "$@" 139 | } 140 | 141 | test_is_older_than() { 142 | local age tstamp 143 | 144 | set -- \ 145 | ge 1 N/A N/A \ 146 | ge 1 newer N/A \ 147 | ge 1 newer-empty N/A \ 148 | ge 1 newer/file N/A \ 149 | ge 1 non-existent N/A \ 150 | eq 0 newer newer \ 151 | ge 1 newer newer-empty \ 152 | eq 0 newer newer/file \ 153 | ge 1 newer non-existent \ 154 | ge 1 newer older \ 155 | ge 1 newer older-empty \ 156 | ge 1 newer older/file \ 157 | eq 0 newer-empty newer \ 158 | ge 1 newer-empty newer-empty \ 159 | eq 0 newer-empty newer/file \ 160 | ge 1 newer-empty non-existent \ 161 | ge 1 newer-empty older \ 162 | ge 1 newer-empty older-empty \ 163 | ge 1 newer-empty older/file \ 164 | ge 1 newer/file newer \ 165 | ge 1 newer/file newer-empty \ 166 | ge 1 newer/file newer/file \ 167 | ge 1 newer/file non-existent \ 168 | ge 1 newer/file older \ 169 | ge 1 newer/file older-empty \ 170 | ge 1 newer/file older/file \ 171 | eq 0 non-existent newer \ 172 | eq 0 non-existent newer-empty \ 173 | eq 0 non-existent newer/file \ 174 | ge 1 non-existent non-existent \ 175 | eq 0 non-existent older \ 176 | eq 0 non-existent older-empty \ 177 | eq 0 non-existent older/file \ 178 | eq 0 older newer \ 179 | eq 0 older newer-empty \ 180 | eq 0 older newer/file \ 181 | ge 1 older non-existent \ 182 | eq 0 older older \ 183 | ge 1 older older-empty \ 184 | eq 0 older older/file \ 185 | eq 0 older-empty newer \ 186 | eq 0 older-empty newer-empty \ 187 | eq 0 older-empty newer/file \ 188 | ge 1 older-empty non-existent \ 189 | eq 0 older-empty older \ 190 | ge 1 older-empty older-empty \ 191 | eq 0 older-empty older/file \ 192 | eq 0 older/file newer \ 193 | eq 0 older/file newer-empty \ 194 | eq 0 older/file newer/file \ 195 | ge 1 older/file non-existent \ 196 | ge 1 older/file older \ 197 | ge 1 older/file older-empty \ 198 | ge 1 older/file older/file 199 | 200 | # The mtimes need to be explicitly assigned. Empirical evidence has 201 | # shown that executing mkdir(1) sequentially, with a single operand 202 | # each time, does not guarantee the order of the resulting mtimes. 203 | tstamp=197001010000 204 | for age in older newer; do 205 | mkdir "${age}" "${age}-empty" \ 206 | && touch -m -t "${tstamp%0}1" "${age}"/file \ 207 | && touch -m -t "${tstamp}" "${age}" "${age}-empty" \ 208 | || bailout "Couldn't create or adjust the mtimes of the sample files" 209 | tstamp=197001010100 # add an hour 210 | done 211 | 212 | callback() { 213 | shift 214 | test_description="is_older_than $(quote_args "$@")" 215 | is_older_than "$@" 216 | } 217 | 218 | iterate_tests 4 "$@" 219 | } 220 | 221 | test_get_bootparam() { 222 | local cmdline 223 | 224 | cmdline="foo gentoo=bar,baz quux" 225 | set -- \ 226 | ge 1 "${cmdline}" N/A \ 227 | ge 1 "${cmdline}" '' \ 228 | ge 1 "gentoo=" '' \ 229 | ge 1 "${cmdline}" foo \ 230 | eq 0 "${cmdline}" bar \ 231 | eq 0 "foo gentoo=gentoo=1,bar baz" bar \ 232 | eq 0 "foo gentoo=bar,gentoo=1 baz" bar \ 233 | eq 0 "${cmdline}" baz \ 234 | ge 1 "${cmdline}" bar,baz \ 235 | eq 0 "foo gentoo=bar,gentoo=1 baz" gentoo=1 \ 236 | eq 0 "foo gentoo=gentoo=1,bar baz" gentoo=1 \ 237 | ge 1 "${cmdline}" quux 238 | 239 | callback() { 240 | local cmdline 241 | 242 | cmdline=$2 243 | shift 2 244 | test_description="get_bootparam $(quote_args "$@")" 245 | printf '%s\n' "${cmdline}" | get_bootparam "$@" 246 | } 247 | 248 | iterate_tests 4 "$@" 249 | } 250 | 251 | test_esyslog() { 252 | set -- \ 253 | ge 1 0 N/A N/A N/A \ 254 | ge 1 0 debug N/A N/A \ 255 | eq 0 0 debug user N/A \ 256 | eq 0 0 debug user '' \ 257 | eq 0 1 debug user message 258 | 259 | logger() { 260 | # esyslog() ignores empty messages. By overriding logger(1), it 261 | # can be determined whether a message would have been logged. 262 | printf '1\n' 263 | } 264 | 265 | callback() { 266 | local logged should_log 267 | 268 | should_log=$2 269 | shift 2 270 | test_description="esyslog $(quote_args "$@")" 271 | # shellcheck disable=2034 272 | logged=$(EINFO_LOG=1; esyslog "$@") 273 | case $? in 274 | 0) 275 | test "${logged:-0}" -eq "${should_log}" 276 | ;; 277 | *) 278 | return "$?" 279 | esac 280 | } 281 | 282 | iterate_tests 6 "$@" 283 | } 284 | 285 | test_is_identifier() { 286 | set -- \ 287 | ge 1 '' \ 288 | ge 1 _ \ 289 | ge 1 0 \ 290 | ge 1 0a \ 291 | ge 1 0Z \ 292 | ge 1 9 \ 293 | ge 1 9a \ 294 | ge 1 9Z \ 295 | ge 1 /a \ 296 | ge 1 /Z \ 297 | ge 1 .a \ 298 | ge 1 .Z \ 299 | ge 1 '[a' \ 300 | ge 1 '[Z' \ 301 | ge 1 '`a' \ 302 | ge 1 '`Z' \ 303 | ge 1 '{a' \ 304 | ge 1 '{Z' \ 305 | ge 1 '|a' \ 306 | ge 1 '|Z' \ 307 | ge 1 a/ \ 308 | ge 1 Z/ \ 309 | ge 1 a. \ 310 | ge 1 Z. \ 311 | ge 1 'a[' \ 312 | ge 1 'Z[' \ 313 | ge 1 'a`' \ 314 | ge 1 'Z`' \ 315 | ge 1 'a{' \ 316 | ge 1 'Z{' \ 317 | ge 1 'a|' \ 318 | ge 1 'Z|' \ 319 | eq 0 a \ 320 | eq 0 Z \ 321 | eq 0 __ \ 322 | eq 0 _a \ 323 | eq 0 _Z \ 324 | eq 0 a_ \ 325 | eq 0 Z_ \ 326 | eq 0 a_a \ 327 | eq 0 a_Z \ 328 | eq 0 Z_a \ 329 | eq 0 Z_Z \ 330 | eq 0 a0 \ 331 | eq 0 a9 \ 332 | eq 0 Z0 \ 333 | eq 0 Z9 \ 334 | eq 0 a_0 \ 335 | eq 0 a_9 \ 336 | eq 0 Z_0 \ 337 | eq 0 Z_9 338 | 339 | callback() { 340 | shift 341 | test_description="is_identifier $(quote_args "$@")" 342 | is_identifier "$@" 343 | } 344 | 345 | iterate_tests 3 "$@" 346 | } 347 | 348 | test_is_int() { 349 | set -- \ 350 | ge 1 N/A \ 351 | ge 1 ' ' \ 352 | ge 1 ' 1 ' \ 353 | ge 1 '' \ 354 | ge 1 +1 \ 355 | ge 1 +008 \ 356 | ge 1 -008 \ 357 | ge 1 008 \ 358 | ge 1 x \ 359 | eq 0 0 \ 360 | eq 0 1 \ 361 | eq 0 -1 \ 362 | eq 0 123456789 363 | 364 | callback() { 365 | shift 366 | test_description="is_int $(quote_args "$@")" 367 | is_int "$@" 368 | } 369 | 370 | iterate_tests 3 "$@" 371 | } 372 | 373 | test_is_visible() { 374 | set -- \ 375 | ge 1 '' \ 376 | ge 1 ' ' \ 377 | ge 1 "$(printf '\t')" \ 378 | ge 1 "$(printf '\a')" \ 379 | eq 0 . \ 380 | eq 0 ' . ' \ 381 | eq 0 "$(printf '\t.\t')" \ 382 | eq 0 "$(printf '\a.\a')" 383 | 384 | callback() { 385 | shift 386 | test_description="_is_visible $(quote_args "$@")" 387 | _is_visible "$@" 388 | } 389 | 390 | iterate_tests 3 "$@" 391 | } 392 | 393 | test_yesno() { 394 | set -- \ 395 | eq 0 yes \ 396 | eq 0 YES \ 397 | eq 0 Yes \ 398 | eq 0 true \ 399 | eq 0 TRUE \ 400 | eq 0 true \ 401 | eq 0 on \ 402 | eq 0 ON \ 403 | eq 0 On \ 404 | eq 0 1 \ 405 | eq 0 truthful_nameref \ 406 | ge 1 no \ 407 | ge 1 NO \ 408 | ge 1 No \ 409 | ge 1 false \ 410 | ge 1 FALSE \ 411 | ge 1 False \ 412 | ge 1 off \ 413 | ge 1 OFF \ 414 | ge 1 Off \ 415 | ge 1 0 \ 416 | ge 1 not_a_nameref \ 417 | ge 1 not-a-valid-nameref \ 418 | ge 1 '_"; set -- yes # code injection' 419 | 420 | # shellcheck disable=2034 421 | truthful_nameref=yes 422 | 423 | callback() { 424 | shift 425 | test_description="yesno $(quote_args "$@")" 426 | yesno "$@" 427 | } 428 | 429 | iterate_tests 3 "$@" 430 | } 431 | 432 | test_srandom() { 433 | set -- \ 434 | eq 0 \ 435 | eq 0 \ 436 | eq 0 \ 437 | eq 0 \ 438 | eq 0 \ 439 | eq 0 \ 440 | eq 0 \ 441 | eq 0 \ 442 | eq 0 \ 443 | eq 0 444 | 445 | callback() { 446 | local number 447 | 448 | number=$(srandom) 449 | test_description="srandom ($(( row += 1 ))/10: ${number:-blank})" 450 | is_int "${number}" \ 451 | && awk -v "n=${number}" 'BEGIN { exit !(n >= 0 && n <= 2147483647) }' 452 | } 453 | 454 | row=0 455 | iterate_tests 2 "$@" 456 | } 457 | 458 | test_srandom_forked() 459 | { 460 | set -- \ 461 | eq 0 unforked \ 462 | eq 0 forking 463 | 464 | callback() { 465 | local mode number 466 | 467 | shift 468 | mode=$1 469 | set -- 470 | test_description="srandom equality where $mode" 471 | if [ "${mode}" = "forking" ]; then 472 | srandom 473 | ( srandom ) 474 | srandom 475 | ( srandom ) 476 | else 477 | srandom 478 | srandom 479 | srandom 480 | srandom 481 | fi > random_numbers || return 482 | while read -r number; do 483 | set -- "$@" "${number}" 484 | done < random_numbers 485 | test_description="srandom equality where $mode ($*)" 486 | if [ "${mode}" = "forking" ]; then 487 | test "$#" -eq 4 && test "$1" -ne "$2" && test "$3" -ne "$4" 488 | else 489 | test "$#" -eq 4 && ! trueof_all test "$1" -eq -- "$@" 490 | fi 491 | } 492 | 493 | iterate_tests 3 "$@" 494 | } 495 | 496 | test_newest() { 497 | set -- \ 498 | ge 1 non-existent non-existent \ 499 | ge 1 N/A N/A \ 500 | \ 501 | eq 0 newer/file N/A \ 502 | eq 0 newer/file newer/file \ 503 | eq 0 newer/file non-existent \ 504 | eq 0 newer/file older/file \ 505 | eq 0 newer/file older/file \ 506 | eq 0 non-existent newer/file \ 507 | eq 0 older/file newer/file \ 508 | eq 0 older/file newer/file \ 509 | \ 510 | eq 0 older/file N/A \ 511 | eq 0 older/file older/file \ 512 | eq 0 older/file non-existent \ 513 | eq 0 non-existent older/file \ 514 | 515 | callback() { 516 | shift 517 | test_description="newest $(quote_args "$@")" 518 | row=$(( row + 1 )) 519 | true | 520 | case ${row} in 521 | [1-2]) 522 | newest "$@" 523 | ;; 524 | [3-9]|10) 525 | test "$(newest "$@")" = "newer/file" 526 | ;; 527 | *) 528 | test "$(newest "$@")" = "older/file" 529 | esac 530 | } 531 | 532 | row=0 533 | iterate_tests 4 "$@" 534 | 535 | callback() { 536 | shift 537 | if [ "$#" -eq 0 ]; then 538 | test_description=": | newest" 539 | else 540 | test_description="printf '%s\\0' $(quote_args "$@") | newest" 541 | fi 542 | row=$(( row + 1 )) 543 | { 544 | test "$#" -gt 0 && printf '%s\0' "$@" 545 | } | 546 | case ${row} in 547 | [1-2]) 548 | newest 549 | ;; 550 | [3-9]|10) 551 | test "$(newest)" = "newer/file" 552 | ;; 553 | *) 554 | test "$(newest)" = "older/file" 555 | esac 556 | } 557 | 558 | row=0 559 | iterate_tests 4 "$@" 560 | } 561 | 562 | test_oldest() { 563 | set -- \ 564 | ge 1 non-existent non-existent \ 565 | ge 1 N/A N/A \ 566 | \ 567 | eq 0 newer/file N/A \ 568 | eq 0 newer/file newer/file \ 569 | eq 0 newer/file non-existent \ 570 | eq 0 non-existent newer/file \ 571 | \ 572 | eq 0 newer/file older/file \ 573 | eq 0 non-existent older/file \ 574 | eq 0 older/file N/A \ 575 | eq 0 older/file newer/file \ 576 | eq 0 older/file non-existent \ 577 | eq 0 older/file older/file \ 578 | eq 0 newer/file older/file \ 579 | eq 0 older/file newer/file 580 | 581 | callback() { 582 | shift 583 | test_description="oldest $(quote_args "$@")" 584 | row=$((row + 1)) 585 | true | 586 | case ${row} in 587 | [1-2]) 588 | oldest "$@" 589 | ;; 590 | [3-6]) 591 | test "$(oldest "$@")" = "newer/file" 592 | ;; 593 | *) 594 | test "$(oldest "$@")" = "older/file" 595 | esac 596 | } 597 | 598 | row=0 599 | iterate_tests 4 "$@" 600 | 601 | callback() { 602 | shift 603 | if [ "$#" -eq 0 ]; then 604 | test_description=": | oldest" 605 | else 606 | test_description="printf '%s\\0' $(quote_args "$@") | oldest" 607 | fi 608 | row=$(( row + 1 )) 609 | { 610 | test "$#" -gt 0 && printf '%s\0' "$@" 611 | } | 612 | case ${row} in 613 | [1-2]) 614 | oldest 615 | ;; 616 | [3-6]) 617 | test "$(oldest)" = "newer/file" 618 | ;; 619 | *) 620 | test "$(oldest)" = "older/file" 621 | esac 622 | } 623 | 624 | row=0 625 | iterate_tests 4 "$@" 626 | } 627 | 628 | test_trim() { 629 | set -- \ 630 | eq 0 '' '' \ 631 | eq 0 ' ' '' \ 632 | eq 0 ' ' '' \ 633 | eq 0 ' X' 'X' \ 634 | eq 0 ' X' 'X' \ 635 | eq 0 'X ' 'X' \ 636 | eq 0 ' X Y' 'X Y' \ 637 | eq 0 ' X Y' 'X Y' \ 638 | eq 0 'X Y ' 'X Y' \ 639 | eq 0 'X Y ' 'X Y' \ 640 | eq 0 "$(printf ' \tX')" 'X' \ 641 | eq 0 "$(printf ' \tX\t ')" 'X' \ 642 | eq 0 "$(printf 'X\t ')" 'X' \ 643 | eq 0 "$(printf ' \tX Y')" 'X Y' \ 644 | eq 0 "$(printf ' \tX Y\t ')" 'X Y' \ 645 | eq 0 "$(printf 'X Y\t ')" 'X Y' 646 | 647 | callback() { 648 | shift 649 | test_description="trim $(quote_args "$1") (expecting $(quote_args "$2"))" 650 | test "$(trim "$1")" = "$2" 651 | } 652 | 653 | iterate_tests 4 "$@" 654 | } 655 | 656 | test_hr() { 657 | # shellcheck disable=2183 658 | set -- \ 659 | eq 0 "$(printf '%80s' | tr ' ' -)" N/A N/A \ 660 | eq 0 "$(printf '%80s' | tr ' ' -)" - N/A \ 661 | eq 0 '' - 0 \ 662 | eq 0 - - 1 \ 663 | eq 0 ----------------- - 17 \ 664 | eq 0 '' xyz 0 \ 665 | eq 0 x xyz 1 \ 666 | eq 0 xxxxxxxxxxxxxxxxx xyz 17 667 | 668 | callback() { 669 | local expected 670 | 671 | shift 672 | expected=$1 673 | shift 674 | test_description="hr $(quote_args "$@")" 675 | hr="leakage" 676 | test "$(hr "$@")" = "${expected}" 677 | } 678 | 679 | iterate_tests 5 "$@" 680 | } 681 | 682 | test_whenceforth() { 683 | set -- \ 684 | ge 1 PATH N/A N/A \ 685 | ge 1 PATH . N/A \ 686 | ge 1 PATH unlikely-to-exist N/A \ 687 | ge 1 PATH /var/empty N/A \ 688 | ge 1 PATH /var/empty/nofile N/A \ 689 | eq 0 PATH /bin/sh N/A \ 690 | eq 0 PATH sh N/A \ 691 | ge 1 PATH -x . \ 692 | ge 1 PATH -x unlikely-to-exist \ 693 | ge 1 PATH -x /var/empty \ 694 | ge 1 PATH -x /var/empty/nofile \ 695 | eq 0 PATH -x /bin/sh \ 696 | eq 0 PATH -x sh \ 697 | eq 0 '' -x newer/file \ 698 | eq 0 . -x newer/file \ 699 | eq 0 :/var/empty/x -x newer/file \ 700 | eq 0 /var/empty/x: -x newer/file \ 701 | eq 0 /var/empty/x::/var/empty/y -x newer/file \ 702 | eq 0 '' -x newer/file \ 703 | eq 0 . -x newer/file \ 704 | eq 0 :/var/empty/x -x newer/file \ 705 | eq 0 /var/empty/x: -x newer/file \ 706 | eq 0 /var/empty/x::/var/empty/y -x newer/file \ 707 | eq 0 '' older/file N/A \ 708 | eq 0 . older/file N/A \ 709 | eq 0 :/var/empty/x older/file N/A \ 710 | eq 0 /var/empty/x: older/file N/A \ 711 | eq 0 /var/empty/x::/var/empty/y older/file N/A \ 712 | ge 1 '' -x older/file \ 713 | ge 1 . -x older/file \ 714 | ge 1 :/var/empty/x -x older/file \ 715 | ge 1 /var/empty/x: -x older/file \ 716 | ge 1 /var/empty/x::/var/empty/y -x older/file 717 | 718 | chmod +x newer/file 719 | 720 | callback() { 721 | local path 722 | 723 | shift 724 | path=$1 725 | shift 726 | if [ "${path}" = PATH ]; then 727 | test_description="whenceforth $(quote_args "$@")" 728 | whenceforth "$@" >/dev/null 729 | else 730 | test_description="PATH=${path} whenceforth $(quote_args "$@")" 731 | ( 732 | # If necessary, declare functions to cover the 733 | # utilities that might otherwise be unavailable 734 | # on account of the various values of PATH 735 | # being tested. It cannot be assumed that the 736 | # utilities in question are builtins. 737 | case ${printf_cmd} in 738 | /*) printf() { "${printf_cmd}" "$@"; } 739 | esac 740 | case ${test_cmd} in 741 | /*) test() { "${test_cmd}" "$@"; } 742 | esac 743 | # shellcheck disable=2030 744 | PATH=${path} 745 | whenceforth "$@" >/dev/null 746 | ) 747 | fi 748 | } 749 | 750 | printf_cmd=$(command -v printf) 751 | test_cmd=$(command -v test) 752 | iterate_tests 5 "$@" 753 | } 754 | 755 | test_get_nprocs() { 756 | set -- eq 0 757 | 758 | callback() { 759 | local nproc 760 | 761 | shift 762 | test_description="get_nprocs" 763 | nproc=$(get_nprocs) && is_int "${nproc}" && test "${nproc}" -gt 0 764 | } 765 | 766 | iterate_tests 2 "$@" 767 | } 768 | 769 | test_parallel_run() { 770 | set -- \ 771 | ge 1 N/A N/A \ 772 | eq 0 / N/A \ 773 | ge 1 /var/empty/nonexistent N/A \ 774 | eq 0 / / \ 775 | ge 1 / /var/empty/nonexistent \ 776 | ge 1 /var/empty/nonexistent /var/empty/nonexistent \ 777 | ge 1 /var/empty/nonexistent / 778 | 779 | callback() { 780 | shift 781 | test_description="parallel_run $(quote_args 0 ls "$@")" 782 | parallel_run 0 ls "$@" >/dev/null 2>&1 783 | } 784 | 785 | iterate_tests 4 "$@" 786 | } 787 | 788 | test_is_anyof() { 789 | set -- \ 790 | ge 1 N/A N/A N/A \ 791 | ge 1 x N/A N/A \ 792 | ge 1 x y N/A \ 793 | ge 1 x y z \ 794 | eq 0 x x N/A \ 795 | eq 0 x x y \ 796 | eq 0 x y x 797 | 798 | callback() { 799 | shift 800 | test_description="is_anyof $(quote_args "$@")" 801 | is_anyof "$@" 802 | } 803 | 804 | iterate_tests 5 "$@" 805 | } 806 | 807 | test_is_subset() { 808 | set -- \ 809 | ge 1 N/A N/A N/A N/A N/A \ 810 | ge 1 -- N/A N/A N/A N/A \ 811 | ge 1 -- -- N/A N/A N/A \ 812 | ge 1 -- x N/A N/A N/A \ 813 | ge 1 x -- N/A N/A N/A \ 814 | ge 1 x y N/A N/A N/A \ 815 | ge 1 x y x N/A N/A \ 816 | eq 0 x -- x N/A N/A \ 817 | eq 0 x -- x y N/A \ 818 | eq 0 x -- y x N/A \ 819 | eq 0 x y -- x y \ 820 | eq 0 x y -- y x \ 821 | ge 1 x y -- x z \ 822 | ge 1 y x -- z x \ 823 | ge 1 x z -- x y \ 824 | ge 1 z x -- y x 825 | 826 | callback() { 827 | shift 828 | test_description="is_subset $(quote_args "$@")" 829 | is_subset "$@" 830 | } 831 | 832 | iterate_tests 7 "$@" 833 | } 834 | 835 | test_trueof_all() { 836 | set -- \ 837 | ge 1 N/A N/A N/A N/A N/A \ 838 | ge 1 test -d N/A N/A N/A \ 839 | ge 1 test -d -- N/A N/A \ 840 | ge 1 test -d -- /dev/null N/A \ 841 | ge 1 test -d -- /dev/null /dev/null \ 842 | eq 0 test -d -- / N/A \ 843 | eq 0 test -d -- / / \ 844 | ge 1 test -d -- / /dev/null \ 845 | ge 1 test -d -- /dev/null / 846 | 847 | callback() { 848 | shift 849 | test_description="trueof_all $(quote_args "$@")" 850 | trueof_all "$@" 851 | } 852 | 853 | iterate_tests 7 "$@" 854 | } 855 | 856 | test_trueof_any() { 857 | set -- \ 858 | ge 1 N/A N/A N/A N/A N/A \ 859 | ge 1 test -d N/A N/A N/A \ 860 | ge 1 test -d -- N/A N/A \ 861 | ge 1 test -d -- /dev/null N/A \ 862 | ge 1 test -d -- /dev/null /dev/null \ 863 | eq 0 test -d -- / N/A \ 864 | eq 0 test -d -- / / \ 865 | eq 0 test -d -- / /dev/null \ 866 | eq 0 test -d -- /dev/null / 867 | 868 | callback() { 869 | shift 870 | test_description="trueof_any $(quote_args "$@")" 871 | trueof_any "$@" 872 | } 873 | 874 | iterate_tests 7 "$@" 875 | } 876 | 877 | test_substr() { 878 | set -- \ 879 | ge 1 - foobar N/A N/A \ 880 | ge 1 - foobar '' N/A \ 881 | ge 1 - foobar x N/A \ 882 | ge 1 - foobar '' '' \ 883 | ge 1 - foobar x y \ 884 | eq 0 foobar foobar 1 N/A \ 885 | eq 0 foobar foobar -1 N/A \ 886 | eq 0 foobar foobar 1 7 \ 887 | eq 0 foobar foobar -1 7 \ 888 | eq 0 foo foobar 1 3 \ 889 | eq 0 foo foobar -1 3 \ 890 | eq 0 f foobar 1 1 \ 891 | eq 0 f foobar -1 1 \ 892 | eq 0 '' foobar 1 0 \ 893 | eq 0 '' foobar 1 -1 \ 894 | eq 0 '' foobar 0 0 \ 895 | eq 0 '' foobar 0 -1 \ 896 | eq 0 '' foobar -1 0 \ 897 | eq 0 '' foobar -1 -1 \ 898 | eq 0 bar foobar 4 N/A \ 899 | eq 0 bar foobar 4 4 \ 900 | eq 0 b foobar 4 1 \ 901 | eq 0 '' foobar 4 0 \ 902 | eq 0 '' foobar 4 -1 903 | 904 | callback() { 905 | local expected str 906 | 907 | shift 908 | expected=$1 909 | shift 910 | test_description="substr $(quote_args "$@")" 911 | str=$(substr "$@") && test "${str}" = "${expected}" 912 | } 913 | 914 | iterate_tests 6 "$@" 915 | } 916 | 917 | test_contains_all() { 918 | set -- \ 919 | ge 1 N/A N/A N/A N/A \ 920 | ge 1 ' foo bar ' '' N/A N/A \ 921 | ge 1 ' foo bar ' '' ' ' N/A \ 922 | ge 1 ' foo bar ' '' ' bar' N/A \ 923 | ge 1 ' foo bar ' '' 'foo ' N/A \ 924 | ge 1 ' foo bar ' '' 'foo bar' N/A \ 925 | ge 1 ' foo bar ' ' ' '' N/A \ 926 | ge 1 ' foo bar ' ' ' ' ' N/A \ 927 | ge 1 ' foo bar ' ' ' N/A N/A \ 928 | ge 1 ' foo bar ' ' bar' '' N/A \ 929 | ge 1 ' foo bar ' ' bar' N/A N/A \ 930 | ge 1 ' foo bar ' 'foo ' '' N/A \ 931 | ge 1 ' foo bar ' 'foo ' ' bar' N/A \ 932 | ge 1 ' foo bar ' 'foo ' N/A N/A \ 933 | ge 1 ' foo bar ' 'foo bar' '' N/A \ 934 | ge 1 ' foo bar ' 'foo bar' N/A N/A \ 935 | ge 1 ' foo bar ' N/A N/A N/A \ 936 | ge 1 ' foo bar ' bar foo '' \ 937 | ge 1 ' foo bar ' bar foo ' ' \ 938 | ge 1 ' foo bar ' baz bar foo \ 939 | ge 1 ' foo bar ' fo ba N/A \ 940 | ge 1 ' foo bar ' foo bar '' \ 941 | ge 1 ' foo bar ' foo bar ' ' \ 942 | ge 1 ' foo bar ' foo bar baz \ 943 | ge 1 ' foo bar ' o a N/A \ 944 | ge 1 ' foo bar ' oo ar N/A \ 945 | eq 0 ' foo bar ' foo bar N/A \ 946 | eq 0 ' foo bar ' bar foo N/A 947 | 948 | callback() { 949 | shift 950 | test_description="contains_all $(quote_args "$@")" 951 | contains_all "$@" 952 | } 953 | 954 | iterate_tests 6 "$@" 955 | } 956 | 957 | test_contains_any() { 958 | set -- \ 959 | ge 1 N/A N/A N/A \ 960 | ge 1 'foo bar' N/A N/A \ 961 | ge 1 'foo bar' fo ba \ 962 | ge 1 'foo bar' oo ar \ 963 | ge 1 'foo bar' o a \ 964 | ge 1 'foo bar' 'foo bar' 'foo bar' \ 965 | ge 1 'foo bar' 'foo bar' _ \ 966 | ge 1 'foo bar' _ 'foo bar' \ 967 | ge 1 'foo bar' 'foo ' ' bar' \ 968 | ge 1 'foo bar' 'foo ' _ \ 969 | ge 1 'foo bar' _ ' bar' \ 970 | ge 1 'foo bar' ' bar' _ \ 971 | ge 1 'foo bar' _ 'foo ' \ 972 | ge 1 'foo bar' '' '' \ 973 | ge 1 'foo bar' '' _ \ 974 | ge 1 'foo bar' _ '' \ 975 | ge 1 'foo bar' ' ' ' ' \ 976 | ge 1 'foo bar' ' ' _ \ 977 | ge 1 'foo bar' _ ' ' \ 978 | eq 0 'foo bar' foo bar \ 979 | eq 0 'foo bar' bar foo \ 980 | eq 0 'foo bar' foo _ \ 981 | eq 0 'foo bar' _ bar \ 982 | eq 0 'foo bar' bar _ \ 983 | eq 0 'foo bar' _ foo 984 | 985 | callback() { 986 | shift 987 | test_description="contains_any $(quote_args "$@")" 988 | contains_any "$@" 989 | } 990 | 991 | iterate_tests 5 "$@" 992 | } 993 | 994 | test_quote_args() { 995 | set -- eq 0 996 | 997 | callback() { 998 | local POSIXLY_CORRECT cksum fmt i str 999 | 1000 | test_description="quote_args output test (expecting cksum 380900690)" 1001 | i=0 1002 | # The generator fails to produce the correct ouput in yash 1003 | # unless the effective character type is C/POSIX. However, once 1004 | # launched, yash ignores assignments to the LC_CTYPE variable 1005 | # if in its posix mode. As things stand, there is little point 1006 | # in fixing it because yash also disables the local builtin in 1007 | # its posix mode, causing test-functions to bail out sooner. 1008 | while [ "$((i += 1))" -le 255 ]; do 1009 | fmt=$(printf '\\%o' "$i") 1010 | # shellcheck disable=2059 1011 | str=$(printf "$fmt.") 1012 | quote_args "${str%.}" || break 1013 | done \ 1014 | | cksum \ 1015 | | { read -r cksum _ && test "${cksum}" = "380900690"; } 1016 | } 1017 | 1018 | iterate_tests 2 "$@" 1019 | } 1020 | 1021 | test_assign() { 1022 | set -- \ 1023 | ge 1 N/A N/A \ 1024 | ge 1 '' N/A \ 1025 | ge 1 0 N/A \ 1026 | ge 1 valid_nameref N/A \ 1027 | ge 1 '' marmoset \ 1028 | ge 1 0 marmoset \ 1029 | ge 1 valid_nameref N/A \ 1030 | ge 1 'injection=1 #' comment \ 1031 | eq 0 valid_nameref marmoset 1032 | 1033 | callback() { 1034 | local injection 1035 | 1036 | shift 1037 | test_description="assign $(quote_args "$@")" 1038 | injection= 1039 | assign "$@" 2>/dev/null || test "${injection}" 1040 | } 1041 | 1042 | iterate_tests 4 "$@" 1043 | } 1044 | 1045 | test_deref() { 1046 | set -- \ 1047 | ge 1 N/A N/A \ 1048 | ge 1 '' N/A \ 1049 | ge 1 0 N/A \ 1050 | ge 1 '' '' \ 1051 | ge 1 0 0 \ 1052 | eq 0 valid_nameref N/A \ 1053 | eq 0 valid_nameref assignee \ 1054 | ge 1 PWD 'injection=1 #' 1055 | 1056 | callback() { 1057 | local assignee injection stdout 1058 | 1059 | shift 1060 | test_description="deref $(quote_args "$@")" 1061 | case $# in 1062 | 2) 1063 | assignee= injection= 1064 | deref "$@" \ 1065 | && { test "${assignee}" = "marmoset" || test "${injection}"; } 1066 | ;; 1067 | *) 1068 | stdout=$(deref "$@") && test "${stdout}" = "marmoset" 1069 | ;; 1070 | esac 2>/dev/null 1071 | } 1072 | 1073 | iterate_tests 4 "$@" 1074 | } 1075 | 1076 | test_update_time() { 1077 | local locale 1078 | 1079 | # The yash shell dies upon integer overflow and _update_time() ends up 1080 | # being deactivated for it. Hence, there is no reason to run this test. 1081 | if [ "${YASH_VERSION}" ]; then 1082 | return 1083 | fi 1084 | 1085 | set -- \ 1086 | de_BE de_DE es_ES fr_BE fr_CA fr_FR it_IT nl_BE nl_NL pl_PL \ 1087 | pt_BR pt_PT ru_RU sv_SE 1088 | 1089 | # Try to test a locale for which the radix character isn't U+2E. 1090 | locale=$( 1091 | IFS='|' 1092 | locale -a 2>/dev/null | awk "/^($*)\.(utf8|UTF-8)$/ { print; exit }" 1093 | ) 1094 | if [ "${locale}" ]; then 1095 | set -- eq 0 "$locale" 1096 | else 1097 | set -- 1098 | fi 1099 | 1100 | # Also test the currently effective locale, whichever it may be. 1101 | set -- "$@" eq 0 '' 1102 | 1103 | callback() { 1104 | local genfun_time 1105 | 1106 | shift 1107 | if [ "$1" ]; then 1108 | test_description="LC_ALL=$1 _update_time" 1109 | genfun_time=$(LC_ALL=$1; _update_time && printf %s "${genfun_time}") 1110 | else 1111 | test_description="_update_time" 1112 | genfun_time=$(_update_time && printf %s "${genfun_time}") 1113 | fi 1114 | case $? in 1115 | 0) 1116 | is_int "${genfun_time}" 1117 | ;; 1118 | 2) 1119 | # Unsupported for the platform and therefore untestable. 1120 | true 1121 | ;; 1122 | *) 1123 | false 1124 | esac 1125 | } 1126 | 1127 | iterate_tests 3 "$@" 1128 | } 1129 | 1130 | test_should_throttle() { 1131 | local bits max_int 1132 | 1133 | # The yash shell dies upon integer overflow and _update_time() ends up 1134 | # being deactivated for it. Hence, there is no reason to run this test. 1135 | if [ "${YASH_VERSION}" ]; then 1136 | return 1137 | fi 1138 | 1139 | genfun_time= 1140 | bits=30 1141 | while [ "${bits}" -lt 128 ]; do 1142 | # Dash is buggy and fails to handle $(( 1 << ++bits )). 1143 | bits=$(( bits + 1 )) 1144 | case $(( max_int = 1 << bits )) in 1145 | -*) 1146 | max_int=$(( max_int - 1 )) 1147 | genfun_time=$(( max_int - 4 )) 1148 | break 1149 | esac 1150 | done 1151 | 1152 | if [ ! "${genfun_time}" ]; then 1153 | bailout "Failed to calculate the maximum possible integer value" 1154 | fi 1155 | 1156 | # For the first test, genfun_last_time is not yet known. Therefore, the 1157 | # return value should always be 1. For the fifth test, integer overflow 1158 | # is expected to occur. Again, the return value should always be 1. 1159 | set -- \ 1160 | ge 1 "${max_int}" \ 1161 | eq 0 2 \ 1162 | ge 1 1 \ 1163 | ge 1 0 \ 1164 | ge 1 2 \ 1165 | eq 0 2 \ 1166 | ge 1 1 \ 1167 | ge 1 0 1168 | 1169 | _update_time() { 1170 | true 1171 | } 1172 | 1173 | callback() { 1174 | shift 1175 | test_description="_should_throttle $1 (${genfun_time}, $(( genfun_time += 1 )))" 1176 | _should_throttle "$1" 1177 | } 1178 | 1179 | iterate_tests 3 "$@" 1180 | } 1181 | 1182 | iterate_tests() { 1183 | local code i j passed slice_width total 1184 | 1185 | slice_width=$1 1186 | shift 1187 | total=$(( $# / slice_width )) 1188 | passed=0 1189 | i=0 1190 | while [ "$((i += 1))" -le "${total}" ]; do 1191 | code="callback" 1192 | j=1 1193 | while [ "$((j += 1))" -le "${slice_width}" ]; do 1194 | if eval "[ \"\$${j}\" = N/A ]"; then 1195 | break 1196 | else 1197 | code="${code} \"\$${j}\"" 1198 | fi 1199 | done 1200 | eval "${code}" 1201 | retval=$? 1202 | if test "${retval}" -"$1" "$2"; then 1203 | passed=$((passed + 1)) 1204 | else 1205 | printf 'not ' 1206 | fi 1207 | printf 'ok %d - %s (test %d -%s %d)\n' \ 1208 | "$((testnum += 1))" "${test_description}" "${retval}" "$1" "$2" 1209 | shift "${slice_width}" 1210 | done 1211 | return "$(( passed < total ))" 1212 | } 1213 | 1214 | printf 'TAP version 13\n' 1215 | 1216 | unset -v global_tmpdir 1217 | 1218 | # PATH is redefined to prevent ebuild-helpers such as die from interfering. 1219 | export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/opt/pkg/bin 1220 | export TEST_GENFUNCS=1 1221 | export TZ=UTC 1222 | testnum=0 1223 | rc=0 1224 | 1225 | if [ "${PORTAGE_BIN_PATH}" ] && [ "${S}" ]; then 1226 | # shellcheck disable=2034 1227 | genfun_basedir=${S} 1228 | fi 1229 | 1230 | if ! test_local; then 1231 | # Currently, this test is known to fail for ksh93 and yash. As regards 1232 | # the former, the commonly implemented behaviour of "local" can be 1233 | # approximated with "typeset". However, to use typeset in this way 1234 | # requires the use of the function f { ...; } syntax instead of the 1235 | # POSIX-compatible f() compound-command syntax. Further, ksh93 1236 | # implements static scoping. As regards the latter, yash is rather 1237 | # stringent and simply disables its local builtin if in its posix mode. 1238 | # Running yash as "sh" would be one way of activating said mode. 1239 | rc=1 1240 | elif ! GENFUN_MODULES="portage rc" . ./functions.sh; then 1241 | bailout "Couldn't source ./functions.sh" 1242 | else 1243 | assign_tmpdir 1244 | test_chdir || rc=1 1245 | test_ebegin || rc=1 1246 | test_is_older_than || rc=1 1247 | test_get_bootparam || rc=1 1248 | test_esyslog || rc=1 1249 | test_is_identifier || rc=1 1250 | test_is_int || rc=1 1251 | test_is_visible || rc=1 1252 | test_yesno || rc=1 1253 | test_die || rc=1 1254 | test_edo || rc=1 1255 | if ! test_srandom; then 1256 | rc=1 1257 | else 1258 | test_srandom_forked || rc=1 1259 | fi 1260 | test_newest || rc=1 1261 | test_oldest || rc=1 1262 | test_trim || rc=1 1263 | test_hr || rc=1 1264 | test_whenceforth || rc=1 1265 | test_parallel_run || rc=1 1266 | test_is_anyof || rc=1 1267 | #test_is_subset || rc=1 1268 | test_trueof_all || rc=1 1269 | test_trueof_any || rc=1 1270 | #test_substr || rc=1 1271 | test_contains_all || rc=1 1272 | test_contains_any || rc=1 1273 | test_quote_args || rc=1 1274 | test_assign || rc=1 1275 | test_deref || rc=1 1276 | test_update_time || rc=1 1277 | test_should_throttle || rc=1 1278 | fi 1279 | 1280 | cleanup_tmpdir 1281 | 1282 | printf '1..%d\n' "${testnum}" 1283 | 1284 | exit "${rc}" 1285 | --------------------------------------------------------------------------------