diff options
author | 2024-06-12 13:16:37 +0100 | |
---|---|---|
committer | 2024-06-23 22:18:36 +0100 | |
commit | 2a58c0e462538b7fb2d12cd95157a9aaf2b7f7ff (patch) | |
tree | 2c69bf04647bc68472b3bf6bab4d3e9f183529e5 | |
parent | Add the is_anyof() and is_subset() functions (diff) | |
download | gentoo-functions-2a58c0e462538b7fb2d12cd95157a9aaf2b7f7ff.tar.gz gentoo-functions-2a58c0e462538b7fb2d12cd95157a9aaf2b7f7ff.tar.bz2 gentoo-functions-2a58c0e462538b7fb2d12cd95157a9aaf2b7f7ff.zip |
Render gentoo-functions modular in nature
For many years, the implied purpose of gentoo-functions has been to
provided parallel implementations of utilities provided by OpenRC, along
with a handful of peripheral functions. It is probably also fair to say
that it has not seen much in the way of maintenance until comparatively
recently. As of the present day, the status quo is not ideal. For one
thing, the library has never been particularly useful beyond this
definition. It is my hope that some of the recently added functions will
be well received by those needing to write effective shell scripts in
Gentoo for a number of relevant tasks. Certainly, there remains ample
room for improvement in that regard.
For another thing, the implementation of gentoo-functions is presently
inflexible. For instance, it is impossible to source the functions from
an OpenRC runscript without overriding the OpenRC implementations. Nor
may one source the functions from an ebuild or eclass without overriding
the Portage implementations. Indeed, it is has become something of a
mess. Not only does gentoo-functions implement a number of functions
that shadow the OpenRC implementations but so does Portage, owing to the
existence of its "isolated-functions.sh" unit. What's more, the various
implementations are of varying quality and do not necessarily behave in
the same manner.
This commit aims to address some of these issues by rendering
gentoo-functions modular in nature. It establishes the premise of having
a core library, with collections of additional functions being
optionally declarable. As such, all of the functions that shadow OpenRC
have been relocated to a unit named "rc.sh". This first change
encompasses the following public functions:
- ebegin
- eend
- eerrorn
- eindent
- einfon
- eoutdent
- esyslog
- ewarnn
- ewend
- get_bootparam
- is_older_than
- veend
- vewend
- yesno
Similarly, all of the functions that exclusively shadow Portage have
been relocated to a unit named "portage.sh". This second change
encompasses the following public functions:
- die
- edo
- eqatag
- eqawarn
The functions that remain in the "functions.sh" unit may now be
considered as core functions. To accommodate all of this, a new
GENFUN_MODULES variable is supported, whose behaviour is described
herewith.
If GENFUN_MODULES is found to be set at the time of "functions.sh" being
sourced, it shall be taken as a list of zero or more blank-separated
words. In turn, these words shall be taken as the basenames of
potentially available modules - not including the .sh suffix.
Presently, the only supported module names are "rc" and "portage".
Should either or both of these names be present, their respective units
shall be automatically sourced. If neither are present, no additional
units shall be sourced. Consequently, it becomes possible for a consumer
of gentoo-functions to request that only the core functions be declared
by writing:
GENFUN_MODULES= . /lib/gentoo/functions.sh
If, on the other hand, GENFUN_MODULES is found not to be set then
heuristics shall be employed to determine which of the additional units
should be sourced. The intent of these heuristics is twofold. Firstly,
to maintain an adequate degree of backward-compatibility and, secondly,
to act as is appropriate based on the characteristics of the operating
environment. The exact behaviour of these heuristics is as follows.
If the present shell is neither executing a runscript nor a subprocess
of one, the ensuing behaviour shall be as if "rc" had initially been
among the names defined by the GENFUN_MODULES variable.
If the present shell is not a subprocess of portage, the ensuing
behaviour shall be as if "portage" had initially been among the names
defined by the GENFUN_MODULES variable.
Signed-off-by: Kerin Millar <kfm@plushkava.net>
-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 |