summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--functions.sh633
-rw-r--r--functions/portage.sh136
-rw-r--r--functions/rc.sh462
-rw-r--r--meson.build5
-rwxr-xr-xtest-functions6
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