diff options
-rw-r--r-- | functions.sh | 633 | ||||
-rw-r--r-- | functions/portage.sh | 136 | ||||
-rw-r--r-- | functions/rc.sh | 462 | ||||
-rw-r--r-- | meson.build | 5 | ||||
-rwxr-xr-x | test-functions | 6 |
5 files changed, 683 insertions, 559 deletions
diff --git a/functions.sh b/functions.sh index 166f184..d80a49c 100644 --- a/functions.sh +++ b/functions.sh @@ -1,4 +1,4 @@ -# Copyright 1999-2023 Gentoo Authors +# Copyright 1999-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # shellcheck shell=sh disable=2209,3043 @@ -15,20 +15,16 @@ # BASH_VERSINFO : whether bash-specific features may be employed # BASHPID : may be used by _update_columns() to detect subshells # COLUMNS : may be used by _update_columns() to get the column count -# EERROR_QUIET : whether error printing functions should be silenced -# EINFO_LOG : whether printing functions should call esyslog() -# EINFO_QUIET : whether info message printing functions should be silenced -# EINFO_VERBOSE : whether v-prefixed functions should do anything # EPOCHREALTIME : potentially used by _update_time() to get the time -# IFS : multiple message operands are joined by its first character -# INSIDE_EMACS : whether to work around an emacs-specific bug in _eend() -# NO_COLOR : whether colored output should be suppressed +# GENFUN_MODULES : which of the optional function collections must be sourced +# IFS : multiple warn() operands are joined by its first character +# INVOCATION_ID : used by from_unit() # PORTAGE_BIN_PATH : used by from_portage() -# RC_NOCOLOR : like NO_COLOR but deprecated -# TEST_GENFUNCS : used for testing the behaviour of get_bootparam() -# TERM : may influence message formatting and whether color is used +# RC_OPENRC_PID : used by from_runscript() +# SYSTEMD_EXEC_PID : used by from_unit() +# TERM : used to detect dumb terminals -################################################################################ +#------------------------------------------------------------------------------# # # A safe wrapper for the cd builtin. To run cd "$dir" is problematic because: @@ -52,262 +48,6 @@ chdir() } # -# Prints a diagnostic message prefixed with the basename of the running script -# before exiting. It shall preserve the value of $? as it was at the time of -# invocation unless its value was 0, in which case the exit status shall be 1. -# -if ! command -v die >/dev/null; then - die() - { - case $? in - 0) - genfun_status=1 - ;; - *) - genfun_status=$? - esac - warn "$@" - exit "${genfun_status}" - } -fi - -# -# Prints a message indicating the onset of a given process, provided that -# EINFO_QUIET is false. It is expected that eend eventually be called, so as to -# indicate whether the process completed successfully or not. -# -ebegin() -{ - local msg - - if ! yesno "${EINFO_QUIET}"; then - msg=$* - while _ends_with_newline "${msg}"; do - msg=${msg%"${genfun_newline}"} - done - _eprint "${GOOD}" "${msg} ...${genfun_newline}" - fi -} - -# -# Takes the positional parameters as the definition of a simple command then -# prints the command as an informational message with einfo before executing it. -# Should the command fail, a diagnostic message shall be printed and the shell -# be made to exit by calling the die function. -# -edo() -{ - genfun_cmd=$(quote_args "$@") - einfo "Executing: ${genfun_cmd}" - "$@" || die "Failed to execute command: ${genfun_cmd}" -} - -# -# Prints an indicator to convey the completion of a given process, provided that -# EINFO_QUIET is false. It is expected that it be paired with an earlier call to -# ebegin. The first parameter shall be taken as an exit status value, making it -# possible to distinguish between success and failure. If unspecified, it shall -# default to 0. The remaining parameters, if any, shall be taken as a diagnostic -# message to convey as an error where the exit status is not 0. -# -eend() -{ - GENFUN_CALLER=${GENFUN_CALLER:-eend} _eend eerror "$@" -} - -# -# Declare the eerror, einfo and ewarn functions. These wrap errorn, einfon and -# ewarnn respectively, the difference being that a newline is appended. -# -for _ in eerror einfo ewarn; do - eval " - $_ () - { - ${_}n \"\${*}\${genfun_newline}\" - } - " -done - -# -# Prints an error message without appending a newline, provided that -# EERROR_QUIET is false. If printed, the message shall also be conveyed to the -# esyslog function. -# -eerrorn() -{ - if ! yesno "${EERROR_QUIET}"; then - _eprint "${BAD}" "$@" >&2 - esyslog "daemon.err" "${0##*/}" "$@" - fi - return 1 -} - -# -# Decreases the level of indentation used by various printing functions. If no -# numerical parameter is given, or if it is negative, increase by 2 spaces. -# -eindent() -{ - if ! is_int "$1" || [ "$1" -le 0 ]; then - set -- 2 - fi - _esetdent "$(( ${#genfun_indent} + $1 ))" -} - -# -# Prints an informational message without appending a newline, provided that -# EINFO_QUIET is false. -# -einfon() -{ - if ! yesno "${EINFO_QUIET}"; then - _eprint "${GOOD}" "$@" - fi -} - -# -# Decreases the level of indentation used by various printing functions. If no -# numerical parameter is given, or if it is negative, decrease by 2 spaces. -# -eoutdent() -{ - if ! is_int "$1" || [ "$1" -le 0 ]; then - set -- 2 - fi - _esetdent "$(( ${#genfun_indent} - $1 ))" -} - -# -# This is based on the eqatag function defined by isolated-functions.sh in -# portage. If the first parameter is the -v option, it shall be disregarded. -# Discounting said option, at least one parameter is required, which shall be -# taken as a tag name. Thereafter, zero or more parameters shall be accepted in -# the form of "key=val", followed by zero or more parameters beginning with a -# <slash>. An object shall be composed in which the tag is the value of a "tag" -# key, the key/value pairs the value of a "data" key, and the <slash>-prefixed -# parameters the value of a "files" key. The resulting object shall be rendered -# as JSON by jq(1) before being logged by the logger(1) utility. -# -eqatag() -{ - local arg i json positional tag - - case ${genfun_has_jq} in - 0) - return 1 - ;; - '') - if ! hash jq 2>/dev/null; [ "$(( genfun_has_jq = $? ))" -eq 0 ]; then - warn "eqatag: this function requires that jq be installed" - return 1 - fi - esac - # Acknowledge the -v option for isolated-functions API compatibility. - if [ "$1" = "-v" ]; then - shift - fi - if [ "$#" -eq 0 ]; then - warn "eqatag: no tag specified" - return 1 - fi - positional=0 - tag=$1 - shift - i=0 - for arg; do - if [ "$(( i += 1 ))" -eq 1 ]; then - set -- - fi - case ${arg} in - [!=/]*=?*) - if [ "${positional}" -eq 1 ]; then - _warn_for_args eqatag "${arg}" - return 1 - fi - set -- "$@" --arg "${arg%%=*}" "${arg#*=}" - ;; - /*) - if [ "${positional}" -eq 0 ]; then - set -- "$@" --args -- - positional=1 - fi - set -- "$@" "${arg}" - ;; - *) - _warn_for_args eqatag "${arg}" - return 1 - esac - done - json=$( - jq -cn '{ - eqatag: { - tag: $ARGS.named["=tag"], - data: $ARGS.named | with_entries(select(.key | startswith("=") | not)), - files: $ARGS.positional - } - }' --arg "=tag" "${tag}" "$@" - ) \ - && logger -p user.debug -t "${0##*/}" -- "${json}" -} - -# -# Prints a QA warning message, provided that EINFO_QUIET is false. If printed, -# the message shall also be conveyed to the esyslog function. For now, this is -# implemented merely as an ewarn wrapper. -# -eqawarn() -{ - ewarn "$@" -} - -# -# Invokes the logger(1) utility, provided that EINFO_LOG is true. The first -# parameter shall be taken as a priority level, the second as the message tag, -# and the remaining parameters as the message to be logged. -# -esyslog() -{ - local pri tag msg - - if [ "$#" -lt 2 ]; then - warn "esyslog: too few arguments (got $#, expected at least 2)" - return 1 - elif yesno "${EINFO_LOG}" && hash logger 2>/dev/null; then - pri=$1 - tag=$2 - shift 2 - msg=$* - if _is_visible "${msg}"; then - # This is not strictly portable because POSIX defines - # no options whatsoever for logger(1). - logger -p "${pri}" -t "${tag}" -- "${msg}" - fi - fi -} - -# -# Prints a warning message without appending a newline, provided that -# EINFO_QUIET is false. If printed, the message shall also be conveyed to the -# esyslog function. -# -ewarnn() -{ - if ! yesno "${EINFO_QUIET}"; then - _eprint "${WARN}" "$@" >&2 - esyslog "daemon.warning" "${0##*/}" "$@" - fi -} - -# -# This behaves as the eend function does, except that the given diagnostic -# message shall be presented as a warning rather than an error. -# -ewend() -{ - GENFUN_CALLER=${GENFUN_CALLER:-ewend} _eend ewarn "$@" -} - -# # Determines whether the current shell is a subprocess of portage. # from_portage() @@ -334,41 +74,6 @@ from_unit() } # -# Determines whether the kernel cmdline contains the specified parameter as a -# component of a comma-separated list specified in the format of gentoo=<list>. -# -get_bootparam() -( - # Gentoo cmdline parameters are comma-delimited, so a search - # string containing a comma must not be allowed to match. - # Similarly, the empty string must not be allowed to match. - case $1 in ''|*,*) return 1 ;; esac - - # Reset the value of IFS because there is no telling what it may be. - IFS=$(printf ' \n\t') - - if [ "${TEST_GENFUNCS}" = 1 ]; then - read -r cmdline - else - read -r cmdline < /proc/cmdline - fi || return - - # Disable pathname expansion. The definition of this function - # is a compound command that incurs a subshell. Therefore, the - # prior state of the option does not need to be recalled. - set -f - for opt in ${cmdline}; do - gentoo_opt=${opt#gentoo=} - if [ "${opt}" != "${gentoo_opt}" ]; then - case ,${gentoo_opt}, in - *,"$1",*) return 0 - esac - fi - done - return 1 -) - -# # Determines whether OpenRC appears to be operational as a service manager in # the context of the present root filesystem namespace. # @@ -469,28 +174,6 @@ is_anyof() } # -# Takes the first parameter as a reference file/directory then determines -# whether any of the following parameters refer to newer files/directories. -# -is_older_than() -{ - local ref - - if [ "$#" -eq 0 ]; then - warn "is_older_than: too few arguments (got $#, expected at least 1)" - return 1 - elif [ -e "$1" ]; then - ref=$1 - else - ref= - fi - shift - { test "$#" -gt 0 && printf '%s\0' "$@"; } \ - | "${genfun_bin_find}" -L -files0-from - ${ref:+-newermm} ${ref:+"${ref}"} -printf '\n' -quit \ - | read -r _ -} - -# # Collects the intersection of the parameters up to - but not including - a # sentinel value then determines whether the resulting set is a subset of the # interection of the remaining parameters. If the SENTINEL variable is set and @@ -688,46 +371,6 @@ quote_args() } # -# Declare the vebegin, veerror, veindent, veinfo, veinfon, veoutdent and vewarn -# functions. These differ from their non-v-prefixed counterparts in that they -# only have an effect where EINFO_VERBOSE is true. -# -for _ in vebegin veerror veindent veinfo veinfon veoutdent vewarn; do - eval " - $_ () - { - if yesno \"\${EINFO_VERBOSE}\"; then - ${_#v} \"\$@\" - fi - } - " -done - -veend() -{ - if yesno "${EINFO_VERBOSE}"; then - GENFUN_CALLER=veend eend "$@" - elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then - _warn_for_args veend "$1" - false - else - return "$1" - fi -} - -vewend() -{ - if yesno "${EINFO_VERBOSE}"; then - GENFUN_CALLER=vewend ewend "$@" - elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then - _warn_for_args vewend "$1" - false - else - return "$1" - fi -} - -# # Generates a random uint32 with the assistance of the kernel CSPRNG. # srandom() @@ -822,157 +465,29 @@ whenceforth() && printf '%s\n' "${bin}" ) -# -# Determines whether the first parameter is truthy. The values taken to be true -# are "yes", "true", "on" and "1", whereas their opposites are taken to be -# false. The empty string is also taken to be false. All pattern matching is -# performed case-insensitively. -# -yesno() -{ - local arg - - if [ "$#" -eq 0 ]; then - warn "yesno: too few arguments (got $#, expected 1)" - return 1 - fi - arg=$1 - for _ in 1 2; do - case ${arg} in - [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0|'') - return 1 - ;; - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) - return 0 - esac - if [ "$_" -ne 1 ] || ! is_identifier "$1"; then - break - else - # The value appears to be a legal variable name. Treat - # it as a name reference and try again, once only. - eval "arg=\$$1" - fi - done - _warn_for_args yesno "$@" - false -} - -# -# Called by eend, ewend, veend and vewend. See the definition of eend for an -# overall description of its purpose. -# -_eend() -{ - local col efunc msg retval - - efunc=$1 - shift - if [ "$#" -eq 0 ]; then - retval=0 - elif ! is_int "$1" || [ "$1" -lt 0 ]; then - _warn_for_args "${GENFUN_CALLER}" "$1" - retval=1 - msg= - else - retval=$1 - shift - msg=$* - fi - - if [ "${retval}" -ne 0 ]; then - # If a message was given, print it with the specified function. - if _is_visible "${msg}"; then - "${efunc}" "${msg}" - fi - # Generate an indicator for ebegin's unsuccessful conclusion. - if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then - msg="[ !! ]" - else - msg="${BRACKET}[ ${BAD}!!${BRACKET} ]${NORMAL}" - fi - elif yesno "${EINFO_QUIET}"; then - return "${retval}" - else - # Generate an indicator for ebegin's successful conclusion. - if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then - msg="[ ok ]" - else - msg="${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}" - fi - fi - - if [ "${genfun_tty}" -eq 2 ]; then - # Save the cursor position with DECSC, move it up by one line - # with CUU, position it horizontally with CHA, print the - # indicator, then restore the cursor position with DECRC. - col=$(( genfun_cols > 6 ? genfun_cols - 6 : 1 )) - printf '\0337\033[1A\033[%dG %s\0338' "$(( col + genfun_offset ))" "${msg}" - else - # The standard output refers either to an insufficiently capable - # terminal or to something other than a terminal. Print the - # indicator, using <space> characters to indent to the extent - # that the last character falls on the 80th column. This hinges - # on the fair assumption that a newline was already printed. - printf '%80s\n' "${msg}" - fi - - return "${retval}" -} - -# -# Determines whether the given string is newline-terminated. -# -_ends_with_newline() -{ - test "${genfun_newline}" \ - && ! case $1 in *"${genfun_newline}") false ;; esac -} - -# -# Called by ebegin, eerrorn, einfon, and ewarnn. -# -_eprint() -{ - local color - - color=$1 - shift - - if [ -t 1 ]; then - printf ' %s*%s %s%s' "${color}" "${NORMAL}" "${genfun_indent}" "$*" - else - printf ' * %s%s' "${genfun_indent}" "$*" - fi -} - -# -# Called by eindent, eoutdent, veindent and veoutdent. It is here that the -# variable containing the horizontal whitespace is updated. -# -_esetdent() -{ - if [ "$1" -lt 0 ]; then - set -- 0 - fi - genfun_indent=$(printf "%${1}s" '') -} +#------------------------------------------------------------------------------# # -# Tries to determine whether the terminal supports ECMA-48 SGR color sequences. +# Considers the first parameter as containing zero or more blank-separated words +# then determines whether any of the remaining parameters can be matched in +# their capacity as discrete words. # -_has_color_terminal() +_contains_word() { - local colors + local word wordlist - # The tput(1) invocation is not portable, though ncurses suffices. In - # this day and age, it is exceedingly unlikely that it will be needed. - if _has_dumb_terminal; then - false - elif colors=$(tput colors 2>/dev/null) && is_int "${colors}"; then - test "${colors}" -gt 0 - else - true - fi + wordlist=$1 word=$2 + case ${word} in + ''|*[[:blank:]]*) + ;; + *) + case " ${wordlist} " in + *[[:blank:]]"${word}"[[:blank:]]*) + return + ;; + esac + esac + false } # @@ -984,13 +499,6 @@ _has_dumb_terminal() } # -# Determines whether the first parameter contains any visible characters. -# -_is_visible() -{ - ! case $1 in *[[:graph:]]*) false ;; esac -} - # # See the definitions of oldest() and newest(). # @@ -1125,6 +633,20 @@ _update_tty_level() } # +# Takes the first parameter as the path of a gentoo-functions module then +# determines whether it has been requested by attempting to match its basename +# against the any of the blank-separated words defined by the GENFUN_MODULES +# variable (not including the ".sh" suffix). +# +_want_module() +{ + local basename + + basename=${1##*/} + _contains_word "${GENFUN_MODULES}" "${basename%.sh}" +} + +# # Prints a diagnostic message concerning invalid function arguments. The first # argument shall be taken as a function identifier. The remaining arguments # shall be safely rendered as a part of the diagnostic. @@ -1139,61 +661,56 @@ _warn_for_args() warn "${ident}: invalid argument${plural}: $(quote_args "$@")" } -# All function declarations end here! Initialisation code only from hereon. -# shellcheck disable=2034 -RC_GOT_FUNCTIONS=yes +#------------------------------------------------------------------------------# # This shall be incremented by one upon any change being made to the public API. # It was introduced by gentoo-functions-1.7 with an initial value of 1. # shellcheck disable=2034 GENFUN_API_LEVEL=1 +# If genfun_basedir is unset, set genfun_prefix to the value of EPREFIX, as it +# was at the time of installing gentoo-functions, before setting genfun_basedir +# to the path of the directory to which this file was installed. Otherwise, +# honour its existing value so as to ease the development and testing process. +if [ ! "${genfun_basedir+set}" ]; then + genfun_prefix= + genfun_basedir=${genfun_prefix}/lib/gentoo +fi + +# Store the name of the GNU find binary. Some platforms may have it as "gfind". +hash gfind 2>/dev/null && genfun_bin_find=gfind || genfun_bin_find=find + # Assign the LF ('\n') character for later expansion. POSIX Issue 8 permits # $'\n' but it may take years for it to be commonly implemented. genfun_newline=' ' -# In Emacs, M-x term opens an "eterm-color" terminal, whose implementation of -# the CHA (ECMA-48 CSI) sequence suffers from an off-by-one error. -if [ "${INSIDE_EMACS}" ] && [ "${TERM}" = "eterm-color" ]; then - genfun_offset=-1 -else - genfun_offset=0 -fi - # Store the path to the true binary. It is potentially used by _update_columns. if [ "${BASH}" ]; then genfun_bin_true=$(whenceforth true) fi -# Store the name of the GNU find binary. Some platforms may have it as "gfind". -hash gfind 2>/dev/null && genfun_bin_find=gfind || genfun_bin_find=find - -# Determine whether the use of color is to be wilfully avoided. -if [ -n "${NO_COLOR}" ]; then - # See https://no-color.org/. - RC_NOCOLOR=yes -else - for _ in "$@"; do - case $_ in - --nocolor|--nocolour|-C) - RC_NOCOLOR=yes - break - esac - done +# The GENFUN_MODULES variable acts as a means of selecting modules, which are +# merely optional collections of functions. If unset then set it now. +if [ ! "${GENFUN_MODULES+set}" ]; then + # OpenRC provides various functions and utilities which have long had + # parallel implementations in gentoo-functions. Declare ours only if the + # shell is neither executing a runscript nor is a subprocess of one. + if ! from_runscript; then + GENFUN_MODULES="rc" + fi + # Several functions are available which overlap with functions and + # utilities provided by portage. These exist primarily to make it easier + # to test code outside of ebuilds. Declare them only if the shell is not + # a subprocess of portage. + if ! from_portage; then + GENFUN_MODULES="${GENFUN_MODULES}${GENFUN_MODULES+ }portage" + fi fi -if ! _has_color_terminal || yesno "${RC_NOCOLOR}"; then - unset -v BAD BRACKET GOOD HILITE NORMAL WARN -else - # Define some ECMA-48 SGR sequences for color support. These variables - # are public, in so far as users of the library may be expanding them. - # Conveniently, these sequences are documented by console_codes(4). - BAD=$(printf '\033[31;01m') - BRACKET=$(printf '\033[34;01m') - GOOD=$(printf '\033[32;01m') - # shellcheck disable=2034 - HILITE=$(printf '\033[36;01m') - NORMAL=$(printf '\033[0m') - WARN=$(printf '\033[33;01m') -fi +# Source any modules that have been selected by the GENFUN_MODULES variable. +for _ in "${genfun_basedir}/functions"/*.sh; do + if _want_module "$_"; then + . "$_" || return + fi +done diff --git a/functions/portage.sh b/functions/portage.sh new file mode 100644 index 0000000..3a7d9f9 --- /dev/null +++ b/functions/portage.sh @@ -0,0 +1,136 @@ +# Copyright 2024 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 +# shellcheck shell=sh disable=3043 + +# This file contains alternative implementations for some of the functions and +# utilities provided by portage and its supporting eclasses. Please refer to +# ../functions.sh for coding conventions. + +# The following variables affect initialisation and/or function behaviour. + +# IFS : multiple message operands are joined by its first character +# RC_GOT_FUNCTIONS : whether the rc module may be used for printing messages + +#------------------------------------------------------------------------------# + +# +# Prints a diagnostic message prefixed with the basename of the running script +# before exiting. It shall preserve the value of $? as it was at the time of +# invocation unless its value was 0, in which case the exit status shall be 1. +# +die() +{ + case $? in + 0) + genfun_status=1 + ;; + *) + genfun_status=$? + esac + warn "$@" + exit "${genfun_status}" +} + +# +# Takes the positional parameters as the definition of a simple command then +# prints the command as an informational message with einfo before executing it. +# Should the command fail, a diagnostic message shall be printed and the shell +# be made to exit by calling the die function. +# +edo() +{ + genfun_cmd=$(quote_args "$@") + if [ "${RC_GOT_FUNCTIONS}" ]; then + einfo "Executing: ${genfun_cmd}" + else + printf 'Executing: %s\n' "${genfun_cmd}" + fi + "$@" || die "Failed to execute command: ${genfun_cmd}" +} + +# +# This is based on the eqatag function defined by isolated-functions.sh in +# portage. If the first parameter is the -v option, it shall be disregarded. +# Discounting said option, at least one parameter is required, which shall be +# taken as a tag name. Thereafter, zero or more parameters shall be accepted in +# the form of "key=val", followed by zero or more parameters beginning with a +# <slash>. An object shall be composed in which the tag is the value of a "tag" +# key, the key/value pairs the value of a "data" key, and the <slash>-prefixed +# parameters the value of a "files" key. The resulting object shall be rendered +# as JSON by jq(1) before being logged by the logger(1) utility. +# +eqatag() +{ + local arg i json positional tag + + case ${genfun_has_jq} in + 0) + return 1 + ;; + '') + if ! hash jq 2>/dev/null; [ "$(( genfun_has_jq = $? ))" -eq 0 ]; then + warn "eqatag: this function requires that jq be installed" + return 1 + fi + esac + # Acknowledge the -v option for isolated-functions API compatibility. + if [ "$1" = "-v" ]; then + shift + fi + if [ "$#" -eq 0 ]; then + warn "eqatag: no tag specified" + return 1 + fi + positional=0 + tag=$1 + shift + i=0 + for arg; do + if [ "$(( i += 1 ))" -eq 1 ]; then + set -- + fi + case ${arg} in + [!=/]*=?*) + if [ "${positional}" -eq 1 ]; then + _warn_for_args eqatag "${arg}" + return 1 + fi + set -- "$@" --arg "${arg%%=*}" "${arg#*=}" + ;; + /*) + if [ "${positional}" -eq 0 ]; then + set -- "$@" --args -- + positional=1 + fi + set -- "$@" "${arg}" + ;; + *) + _warn_for_args eqatag "${arg}" + return 1 + esac + done + json=$( + jq -cn '{ + eqatag: { + tag: $ARGS.named["=tag"], + data: $ARGS.named | with_entries(select(.key | startswith("=") | not)), + files: $ARGS.positional + } + }' --arg "=tag" "${tag}" "$@" + ) \ + && logger -p user.debug -t "${0##*/}" -- "${json}" +} + +# +# Prints a QA warning message, provided that EINFO_QUIET is false. If printed, +# the message shall also be conveyed to the esyslog function. For now, this is +# implemented merely as an ewarn wrapper. +# +eqawarn() +{ + if [ "${RC_GOT_FUNCTIONS}" ]; then + ewarn "$@" + else + warn "$@" + fi +} diff --git a/functions/rc.sh b/functions/rc.sh new file mode 100644 index 0000000..519d847 --- /dev/null +++ b/functions/rc.sh @@ -0,0 +1,462 @@ +# Copyright 1999-2024 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 +# shellcheck shell=sh disable=3043 + +# This file contains alternative implementations for some of the functions and +# utilities provided by OpenRC. Please refer to ../functions.sh for coding +# conventions. + +# The following variables affect initialisation and/or function behaviour. + +# EERROR_QUIET : whether error printing functions should be silenced +# EINFO_LOG : whether printing functions should call esyslog() +# EINFO_QUIET : whether info message printing functions should be silenced +# EINFO_VERBOSE : whether v-prefixed functions should do anything +# IFS : multiple message operands are joined by its first character +# INSIDE_EMACS : whether to work around an emacs-specific bug in _eend() +# NO_COLOR : whether colored output should be suppressed +# RC_NOCOLOR : like NO_COLOR but deprecated +# TERM : whether to work around an emacs-specific bug in _eend() +# TEST_GENFUNCS : used for testing the behaviour of get_bootparam() + +#------------------------------------------------------------------------------# + +# +# Prints a message indicating the onset of a given process, provided that +# EINFO_QUIET is false. It is expected that eend eventually be called, so as to +# indicate whether the process completed successfully or not. +# +ebegin() +{ + local msg + + if ! yesno "${EINFO_QUIET}"; then + msg=$* + while _ends_with_newline "${msg}"; do + msg=${msg%"${genfun_newline}"} + done + _eprint "${GOOD}" "${msg} ...${genfun_newline}" + fi +} + +# +# Prints an indicator to convey the completion of a given process, provided that +# EINFO_QUIET is false. It is expected that it be paired with an earlier call to +# ebegin. The first parameter shall be taken as an exit status value, making it +# possible to distinguish between success and failure. If unspecified, it shall +# default to 0. The remaining parameters, if any, shall be taken as a diagnostic +# message to convey as an error where the exit status is not 0. +# +eend() +{ + GENFUN_CALLER=${GENFUN_CALLER:-eend} _eend eerror "$@" +} + +# +# Declare the eerror, einfo and ewarn functions. These wrap errorn, einfon and +# ewarnn respectively, the difference being that a newline is appended. +# +for _ in eerror einfo ewarn; do + eval " + $_ () + { + ${_}n \"\${*}\${genfun_newline}\" + } + " +done + +# +# Prints an error message without appending a newline, provided that +# EERROR_QUIET is false. If printed, the message shall also be conveyed to the +# esyslog function. +# +eerrorn() +{ + if ! yesno "${EERROR_QUIET}"; then + _eprint "${BAD}" "$@" >&2 + esyslog "daemon.err" "${0##*/}" "$@" + fi + return 1 +} + +# +# Decreases the level of indentation used by various printing functions. If no +# numerical parameter is given, or if it is negative, increase by 2 spaces. +# +eindent() +{ + if ! is_int "$1" || [ "$1" -le 0 ]; then + set -- 2 + fi + _esetdent "$(( ${#genfun_indent} + $1 ))" +} + +# +# Prints an informational message without appending a newline, provided that +# EINFO_QUIET is false. +# +einfon() +{ + if ! yesno "${EINFO_QUIET}"; then + _eprint "${GOOD}" "$@" + fi +} + +# +# Decreases the level of indentation used by various printing functions. If no +# numerical parameter is given, or if it is negative, decrease by 2 spaces. +# +eoutdent() +{ + if ! is_int "$1" || [ "$1" -le 0 ]; then + set -- 2 + fi + _esetdent "$(( ${#genfun_indent} - $1 ))" +} + +# +# Invokes the logger(1) utility, provided that EINFO_LOG is true. The first +# parameter shall be taken as a priority level, the second as the message tag, +# and the remaining parameters as the message to be logged. +# +esyslog() +{ + local pri tag msg + + if [ "$#" -lt 2 ]; then + warn "esyslog: too few arguments (got $#, expected at least 2)" + return 1 + elif yesno "${EINFO_LOG}" && hash logger 2>/dev/null; then + pri=$1 tag=$2 + shift 2 + msg=$* + if _is_visible "${msg}"; then + # This is not strictly portable because POSIX defines + # no options whatsoever for logger(1). + logger -p "${pri}" -t "${tag}" -- "${msg}" + fi + fi +} + +# +# Prints a warning message without appending a newline, provided that +# EINFO_QUIET is false. If printed, the message shall also be conveyed to the +# esyslog function. +# +ewarnn() +{ + if ! yesno "${EINFO_QUIET}"; then + _eprint "${WARN}" "$@" >&2 + esyslog "daemon.warning" "${0##*/}" "$@" + fi +} + +# +# This behaves as the eend function does, except that the given diagnostic +# message shall be presented as a warning rather than an error. +# +ewend() +{ + GENFUN_CALLER=${GENFUN_CALLER:-ewend} _eend ewarn "$@" +} + +# +# Determines whether the kernel cmdline contains the specified parameter as a +# component of a comma-separated list specified in the format of gentoo=<list>. +# +get_bootparam() +( + # Gentoo cmdline parameters are comma-delimited, so a search + # string containing a comma must not be allowed to match. + # Similarly, the empty string must not be allowed to match. + case $1 in ''|*,*) return 1 ;; esac + + # Reset the value of IFS because there is no telling what it may be. + IFS=$(printf ' \n\t') + + if [ "${TEST_GENFUNCS}" = 1 ]; then + read -r cmdline + else + read -r cmdline < /proc/cmdline + fi || return + + # Disable pathname expansion. The definition of this function + # is a compound command that incurs a subshell. Therefore, the + # prior state of the option does not need to be recalled. + set -f + for opt in ${cmdline}; do + gentoo_opt=${opt#gentoo=} + if [ "${opt}" != "${gentoo_opt}" ]; then + case ,${gentoo_opt}, in + *,"$1",*) return 0 + esac + fi + done + return 1 +) + +# +# Takes the first parameter as a reference file/directory then determines +# whether any of the following parameters refer to newer files/directories. +# +is_older_than() +{ + local ref + + if [ "$#" -eq 0 ]; then + warn "is_older_than: too few arguments (got $#, expected at least 1)" + return 1 + elif [ -e "$1" ]; then + ref=$1 + else + ref= + fi + shift + { test "$#" -gt 0 && printf '%s\0' "$@"; } \ + | "${genfun_bin_find}" -L -files0-from - ${ref:+-newermm} ${ref:+"${ref}"} -printf '\n' -quit \ + | read -r _ +} + +# +# Declare the vebegin, veerror, veindent, veinfo, veinfon, veoutdent and vewarn +# functions. These differ from their non-v-prefixed counterparts in that they +# only have an effect where EINFO_VERBOSE is true. +# +for _ in vebegin veerror veindent veinfo veinfon veoutdent vewarn; do + eval " + $_ () + { + if yesno \"\${EINFO_VERBOSE}\"; then + ${_#v} \"\$@\" + fi + } + " +done + +veend() +{ + if yesno "${EINFO_VERBOSE}"; then + GENFUN_CALLER=veend eend "$@" + elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then + _warn_for_args veend "$1" + false + else + return "$1" + fi +} + +vewend() +{ + if yesno "${EINFO_VERBOSE}"; then + GENFUN_CALLER=vewend ewend "$@" + elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then + _warn_for_args vewend "$1" + false + else + return "$1" + fi +} + +# +# Determines whether the first parameter is truthy. The values taken to be true +# are "yes", "true", "on" and "1", whereas their opposites are taken to be +# false. The empty string is also taken to be false. All pattern matching is +# performed case-insensitively. +# +yesno() +{ + local arg + + if [ "$#" -eq 0 ]; then + warn "yesno: too few arguments (got $#, expected 1)" + return 1 + fi + arg=$1 + for _ in 1 2; do + case ${arg} in + [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0|'') + return 1 + ;; + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + return 0 + esac + if [ "$_" -ne 1 ] || ! is_identifier "$1"; then + break + else + # The value appears to be a legal variable name. Treat + # it as a name reference and try again, once only. + eval "arg=\$$1" + fi + done + _warn_for_args yesno "$@" + false +} + +#------------------------------------------------------------------------------# + +# +# Called by eend, ewend, veend and vewend. See the definition of eend for an +# overall description of its purpose. +# +_eend() +{ + local col efunc msg retval + + efunc=$1 + shift + if [ "$#" -eq 0 ]; then + retval=0 + elif ! is_int "$1" || [ "$1" -lt 0 ]; then + _warn_for_args "${GENFUN_CALLER}" "$1" + retval=1 + msg= + else + retval=$1 + shift + msg=$* + fi + + if [ "${retval}" -ne 0 ]; then + # If a message was given, print it with the specified function. + if _is_visible "${msg}"; then + "${efunc}" "${msg}" + fi + # Generate an indicator for ebegin's unsuccessful conclusion. + if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then + msg="[ !! ]" + else + msg="${BRACKET}[ ${BAD}!!${BRACKET} ]${NORMAL}" + fi + elif yesno "${EINFO_QUIET}"; then + return "${retval}" + else + # Generate an indicator for ebegin's successful conclusion. + if _update_tty_level <&1; [ "${genfun_tty}" -eq 0 ]; then + msg="[ ok ]" + else + msg="${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}" + fi + fi + + if [ "${genfun_tty}" -eq 2 ]; then + # Save the cursor position with DECSC, move it up by one line + # with CUU, position it horizontally with CHA, print the + # indicator, then restore the cursor position with DECRC. + col=$(( genfun_cols > 6 ? genfun_cols - 6 : 1 )) + printf '\0337\033[1A\033[%dG %s\0338' "$(( col + genfun_offset ))" "${msg}" + else + # The standard output refers either to an insufficiently capable + # terminal or to something other than a terminal. Print the + # indicator, using <space> characters to indent to the extent + # that the last character falls on the 80th column. This hinges + # on the fair assumption that a newline was already printed. + printf '%80s\n' "${msg}" + fi + + return "${retval}" +} + +# +# Determines whether the given string is newline-terminated. +# +_ends_with_newline() +{ + test "${genfun_newline}" \ + && ! case $1 in *"${genfun_newline}") false ;; esac +} + +# +# Called by ebegin, eerrorn, einfon, and ewarnn. +# +_eprint() +{ + local color + + color=$1 + shift + if [ -t 1 ]; then + printf ' %s*%s %s%s' "${color}" "${NORMAL}" "${genfun_indent}" "$*" + else + printf ' * %s%s' "${genfun_indent}" "$*" + fi +} + +# +# Called by eindent, eoutdent, veindent and veoutdent. It is here that the +# variable containing the horizontal whitespace is updated. +# +_esetdent() +{ + if [ "$1" -lt 0 ]; then + set -- 0 + fi + genfun_indent=$(printf "%${1}s" '') +} + +# +# Tries to determine whether the terminal supports ECMA-48 SGR color sequences. +# +_has_color_terminal() +{ + local colors + + # The tput(1) invocation is not portable, though ncurses suffices. In + # this day and age, it is exceedingly unlikely that it will be needed. + if _has_dumb_terminal; then + false + elif colors=$(tput colors 2>/dev/null) && is_int "${colors}"; then + test "${colors}" -gt 0 + else + true + fi +} + +# +# Determines whether the first parameter contains any visible characters. +# +_is_visible() +{ + ! case $1 in *[[:graph:]]*) false ;; esac +} + +#------------------------------------------------------------------------------# + +# Determine whether the use of color is to be wilfully avoided. +if [ -n "${NO_COLOR}" ]; then + # See https://no-color.org/. + RC_NOCOLOR=yes +else + for _; do + case $_ in + --nocolor|--nocolour|-C) + RC_NOCOLOR=yes + break + esac + done +fi + +if ! _has_color_terminal || yesno "${RC_NOCOLOR}"; then + unset -v BAD BRACKET GOOD HILITE NORMAL WARN +else + # Define some ECMA-48 SGR sequences for color support. These variables + # are public, in so far as users of the library may be expanding them. + # Conveniently, these sequences are documented by console_codes(4). + BAD=$(printf '\033[31;01m') + BRACKET=$(printf '\033[34;01m') + GOOD=$(printf '\033[32;01m') + # shellcheck disable=2034 + HILITE=$(printf '\033[36;01m') + NORMAL=$(printf '\033[0m') + WARN=$(printf '\033[33;01m') +fi + +# In Emacs, M-x term opens an "eterm-color" terminal, whose implementation of +# the CHA (ECMA-48 CSI) sequence suffers from an off-by-one error. +if [ "${INSIDE_EMACS}" ] && [ "${TERM}" = "eterm-color" ]; then + genfun_offset=-1 +else + genfun_offset=0 +fi + +# shellcheck disable=2034 +RC_GOT_FUNCTIONS=yes diff --git a/meson.build b/meson.build index 6590240..f7985a4 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,11 @@ install_data( install_dir: 'lib/gentoo' ) +install_subdir( + 'functions', + install_dir: '/lib/gentoo' +) + cc = meson.get_compiler('c') executable( diff --git a/test-functions b/test-functions index 0b987ab..d086e16 100755 --- a/test-functions +++ b/test-functions @@ -664,7 +664,11 @@ export TZ=UTC testnum=0 rc=0 -if ! . ./functions.sh; then +if [ "${PORTAGE_BIN_PATH}" ] && [ "${S}" ]; then + genfun_basedir=${S} +fi + +if ! GENFUN_MODULES="portage rc" . ./functions.sh; then bailout "Couldn't source ./functions.sh" fi |