public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/gentoo-functions:master commit in: functions/, /
@ 2024-06-25  4:06 Sam James
  0 siblings, 0 replies; 4+ messages in thread
From: Sam James @ 2024-06-25  4:06 UTC (permalink / raw
  To: gentoo-commits

commit:     2a58c0e462538b7fb2d12cd95157a9aaf2b7f7ff
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Wed Jun 12 12:16:37 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sun Jun 23 21:18:36 2024 +0000
URL:        https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=2a58c0e4

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 <AT> plushkava.net>

 functions.sh         | 633 ++++++---------------------------------------------
 functions/portage.sh | 136 +++++++++++
 functions/rc.sh      | 462 +++++++++++++++++++++++++++++++++++++
 meson.build          |   5 +
 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:
@@ -51,262 +47,6 @@ chdir()
 	CDPATH= cd -- "$@"
 }
 
-#
-# 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.
 #
@@ -333,41 +73,6 @@ from_unit()
 	has_systemd && test "${SYSTEMD_EXEC_PID}" && test "${INVOCATION_ID}"
 }
 
-#
-# 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.
@@ -468,28 +173,6 @@ is_anyof()
 	false
 }
 
-#
-# 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
@@ -687,46 +370,6 @@ quote_args()
 	EOF
 }
 
-#
-# 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.
 #
@@ -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().
 #
@@ -1124,6 +632,20 @@ _update_tty_level()
 	fi
 }
 
+#
+# 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
@@ -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
 


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [gentoo-commits] proj/gentoo-functions:master commit in: functions/, /
@ 2024-08-02 23:14 Sam James
  0 siblings, 0 replies; 4+ messages in thread
From: Sam James @ 2024-08-02 23:14 UTC (permalink / raw
  To: gentoo-commits

commit:     4e09cd6073e9f7906081231af7511bc74cd1d1bb
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Mon Jul  8 05:24:32 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Mon Jul  8 05:24:32 2024 +0000
URL:        https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=4e09cd60

Add the _find0() helper function

The function is a simple wrapper around GNU find(1) which presumes the
intent to use the -files0-from option to read NUL-delimited path names
from the standard input. The benefit in having it is twofold. Firstly,
the _select_by_mtime() and is_older_than() functions are thereby made a
little easier to read. Secondly, the genfun_bin_find variable is now
initialised lazily.

Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>

 functions.sh    | 33 ++++++++++++++++++++++++++++-----
 functions/rc.sh |  2 +-
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/functions.sh b/functions.sh
index 82f59a3..dd4eef8 100644
--- a/functions.sh
+++ b/functions.sh
@@ -652,6 +652,33 @@ whenceforth()
 
 #------------------------------------------------------------------------------#
 
+#
+# See the definitions of _select_by_mtime() and is_older_than().
+#
+_find0()
+{
+	# Store the name of the GNU find binary, which may be "gfind".
+	hash gfind 2>/dev/null && genfun_bin_find=gfind || genfun_bin_find=find
+
+	_find0()
+	{
+		local opt
+
+		case $1 in
+			-[HL])
+				opt=$1
+				shift
+				set -- "${opt}" -files0-from - "$@"
+				;;
+			*)
+				set -- -files0-from - "$@"
+		esac
+		"${genfun_bin_find}" "$@"
+	}
+
+	_find0 "$@"
+}
+
 #
 # Determines whether the terminal is a dumb one.
 #
@@ -660,7 +687,6 @@ _has_dumb_terminal()
 	! case ${TERM} in *dumb*) false ;; esac
 }
 
-#
 #
 # See the definitions of oldest() and newest().
 #
@@ -674,7 +700,7 @@ _select_by_mtime() {
 	else
 		cat
 	fi \
-	| "${genfun_bin_find}" -files0-from - -maxdepth 0 ! -path "*${genfun_newline}*" -printf '%T+ %p\n' \
+	| _find0 -maxdepth 0 ! -path "*${genfun_newline}*" -printf '%T+ %p\n' \
 	| sort "${sort_opt}" \
 	| { IFS= read -r line && printf '%s\n' "${line#* }"; }
 }
@@ -841,9 +867,6 @@ if [ ! "${genfun_basedir+set}" ]; then
 	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='

diff --git a/functions/rc.sh b/functions/rc.sh
index 2f2dc52..9129b32 100644
--- a/functions/rc.sh
+++ b/functions/rc.sh
@@ -213,7 +213,7 @@ is_older_than()
 	fi
 	shift
 	{ test "$#" -gt 0 && printf '%s\0' "$@"; } \
-	| "${genfun_bin_find}" -L -files0-from - ${ref:+-newermm} ${ref:+"${ref}"} -printf '\n' -quit \
+	| _find0 -L ${ref:+-newermm} ${ref:+"${ref}"} -printf '\n' -quit \
 	| read -r _
 }
 


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [gentoo-commits] proj/gentoo-functions:master commit in: functions/, /
@ 2024-08-11 10:11 Sam James
  0 siblings, 0 replies; 4+ messages in thread
From: Sam James @ 2024-08-11 10:11 UTC (permalink / raw
  To: gentoo-commits

commit:     5a34ca0e2001d70bc2037fc2226ad510e26a3349
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Thu Aug  8 01:27:52 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sun Aug 11 10:10:58 2024 +0000
URL:        https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=5a34ca0e

Avoid unspecified behaviour around simple commands in general

As mentioned by the previous commit, the Shell Command Language leaves
it unspecified as to whether variable assignments affecting the
execution environment of a simple command charged with executing a
function (that is not the implementation of a standard utility) shall
persist after the completion of the function.

It transpires that modifying gentoo-functions so as to steer clear of
this pitfall isn't particularly difficult so this commit does exactly
that. Most of the changes are in test-functions but functions/rc.sh also
required some minor changes regarding the use of the GENFUN_CALLER
variable.

With this, loksh very nearly passes the test suite. There is one
individual test that continues to fail, although it looks as though that
may be caused by a genuine bug on the part of the shell. That will
require investigating in its own right.

Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 functions/rc.sh | 16 +++++++++++-----
 test-functions  | 43 ++++++++++++++++++++++++-------------------
 2 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/functions/rc.sh b/functions/rc.sh
index 12444d1..0c14035 100644
--- a/functions/rc.sh
+++ b/functions/rc.sh
@@ -49,7 +49,8 @@ ebegin()
 #
 eend()
 {
-	GENFUN_CALLER=${GENFUN_CALLER:-eend} _eend eerror "$@"
+	: "${genfun_caller:=eend}"
+	_eend eerror "$@"
 }
 
 #
@@ -161,7 +162,8 @@ ewarnn()
 #
 ewend()
 {
-	GENFUN_CALLER=${GENFUN_CALLER:-ewend} _eend ewarn "$@"
+	: "${genfun_caller:=ewend}"
+	_eend ewarn "$@"
 }
 
 #
@@ -240,7 +242,8 @@ done
 veend()
 {
 	if yesno "${EINFO_VERBOSE}"; then
-		GENFUN_CALLER=veend eend "$@"
+		genfun_caller=veend
+		eend "$@"
 	elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then
 		_warn_for_args veend "$1"
 		false
@@ -252,7 +255,8 @@ veend()
 vewend()
 {
 	if yesno "${EINFO_VERBOSE}"; then
-		GENFUN_CALLER=vewend ewend "$@"
+		genfun_caller=vewend
+		ewend "$@"
 	elif [ "$#" -gt 0 ] && { ! is_int "$1" || [ "$1" -lt 0 ]; }; then
 		_warn_for_args vewend "$1"
 		false
@@ -311,7 +315,7 @@ _eend()
 	if [ "$#" -eq 0 ]; then
 		retval=0
 	elif ! is_int "$1" || [ "$1" -lt 0 ]; then
-		_warn_for_args "${GENFUN_CALLER}" "$1"
+		_warn_for_args "${genfun_caller}" "$1"
 		retval=1
 		msg=
 	else
@@ -320,6 +324,8 @@ _eend()
 		msg=$*
 	fi
 
+	genfun_caller=
+
 	if [ "${retval}" -ne 0 ]; then
 		# If a message was given, print it with the specified function.
 		if _is_visible "${msg}"; then

diff --git a/test-functions b/test-functions
index afc56eb..0b0e127 100755
--- a/test-functions
+++ b/test-functions
@@ -46,19 +46,6 @@ test_local() {
 	return "${retval}"
 }
 
-test_simple_command() {
-	f() { :; }
-	LEAKED=
-	LEAKED=1 f
-	retval=0
-	if [ "${LEAKED}" ]; then
-		printf 'not '
-		retval=1
-	fi
-	printf "ok %d - /bin/sh refrains from leaking environmental changes for simple commands\\n" "$((testnum += 1))"
-	return "${retval}"
-}
-
 test_chdir() {
 	set -- \
 		ge  1          ''  \
@@ -76,13 +63,17 @@ test_chdir() {
 	fi
 
 	callback() {
+		local CDPATH var
+
 		shift
 		test_description="chdir $(quote_args "$@")"
 		if [ "$BASH" ]; then
 			# shellcheck disable=3044
 			shopt -s cdable_vars
 		fi
-		CDPATH=child var=child chdir "$@" \
+		CDPATH=child
+		var=child
+		chdir "$@" \
 		&& test "$PWD" != "$OLDPWD" \
 		&& cd - >/dev/null
 	}
@@ -266,7 +257,7 @@ test_esyslog() {
 		should_log=$2
 		shift 2
 		test_description="esyslog $(quote_args "$@")"
-		logged=$(EINFO_LOG=1 esyslog "$@")
+		logged=$(EINFO_LOG=1; esyslog "$@")
 		case $? in
 			0)
 				test "${logged:-0}" -eq "${should_log}"
@@ -680,10 +671,22 @@ test_whenceforth() {
 			whenceforth "$@" >/dev/null
 		else
 			test_description="PATH=${path} whenceforth $(quote_args "$@")"
-			PATH=${path} whenceforth "$@" >/dev/null
+			(
+				# If necessary, conduct the test with a printf
+				# function in effect, duly covering shells that
+				# do not implement it as a builtin. Otherwise,
+				# it could become unavailable on account of the
+				# various values of PATH being tested.
+				case ${printf_cmd} in
+					/*) printf() { "${printf_cmd}" "$@"; }
+				esac
+				PATH=${path}
+				whenceforth "$@" >/dev/null
+			)
 		fi
 	}
 
+	printf_cmd=$(command -v printf)
 	iterate_tests 5 "$@"
 }
 
@@ -923,6 +926,8 @@ test_contains_any() {
 }
 
 test_quote_args() {
+	local POSIXLY_CORRECT
+
 	testnum=$((testnum + 1))
 	retval=0
 	i=0
@@ -930,7 +935,7 @@ test_quote_args() {
 		fmt=$(printf '\\%o' "$i")
 		# shellcheck disable=2059
 		str=$(printf "$fmt.")
-		POSIXLY_CORRECT= quote_args "${str%.}" || break
+		quote_args "${str%.}" || break
 	done | cksum | {
 		read -r cksum _
 		if [ "${cksum}" != "380900690" ]; then
@@ -1016,7 +1021,7 @@ test_update_time() {
 		shift
 		if [ "$1" ]; then
 			test_description="LC_ALL=$1 _update_time"
-			LC_ALL=$1 genfun_time=$(_update_time && printf %s "${genfun_time}")
+			genfun_time=$(LC_ALL=$1; _update_time && printf %s "${genfun_time}")
 		else
 			test_description="_update_time"
 			genfun_time=$(_update_time && printf %s "${genfun_time}")
@@ -1084,7 +1089,7 @@ if [ "${PORTAGE_BIN_PATH}" ] && [ "${S}" ]; then
 	genfun_basedir=${S}
 fi
 
-if ! test_local || ! test_simple_command; then
+if ! test_local; then
 	rc=1
 elif ! GENFUN_MODULES="portage rc" . ./functions.sh; then
 	bailout "Couldn't source ./functions.sh"


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [gentoo-commits] proj/gentoo-functions:master commit in: functions/, /
@ 2024-08-11 10:11 Sam James
  0 siblings, 0 replies; 4+ messages in thread
From: Sam James @ 2024-08-11 10:11 UTC (permalink / raw
  To: gentoo-commits

commit:     6cf0940b8d336eb35a970af2ffc819f55e3ab429
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Sat Aug 10 05:12:15 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Sun Aug 11 10:11:03 2024 +0000
URL:        https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=6cf0940b

Use the -nt and -ot test primaries again rather than depend on GNU find

As regards the test(1) utility, the POSIX.1-2024 specification defines
the -nt and -ot primaries as standard features. Given that the
specification in question was only recently published, this would not
normally be an adequate reason for using them in gentoo-functions, in
and as of itself. However, I was already aware that the these primaries
are commonly implemented and have been so for years.

So, I decided to evaluate a number of shells and see how things stand
now. Here is a list of the ones that I tested:

- ash (busybox 1.36.1)
- dash 0.5.12
- bash 5.2.26
- ksh 93u+
- loksh 7.5
- mksh 59c
- oksh 7.5
- sh (FreeBSD 14.1)
- sh (NetBSD 10.0)
- sh (OpenBSD 7.5)
- yash 2.56.1

Of these, bash, ksh93, loksh, mksh, oksh, OpenBSD sh and yash appear to
conform with the POSIX-1.2024 specification. The remaining four fail to
conform in one particular respect, which is as follows.

$ touch existent
$ set -- existent nonexistent
$ [ "$1" -nt "$2" ]; echo "$?" # should be 0
1
$ [ "$2" -ot "$1" ]; echo "$?" # should be 0
1

To address this, I discerned a reasonably straightforward workaround
that involves testing both whether the file under consideration exists
and whether the variable keeping track of the newest/oldest file has yet
been assigned to.

As far as I am concerned, the coverage is more than adequate for both
primaries to be used by gentoo-functions. As such, this commit adjusts
the following three functions so as to do exactly that.

- is_older_than()
- newest()
- oldest()

It also removes the following functions, since they are no longer used.

- _find0()
- _select_by_mtime()

With this, GNU findutils is no longer a required runtime dependency. Of
course, should a newly introduced feature of gentoo-functions benefit
from the presence of findutils in the future, there is no reason that it
cannot be brought back in that capacity.

Signed-off-by: Kerin Millar <kfm <AT> plushkava.net>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 functions.sh    | 157 +++++++++++++++++++++++++++++++++-----------------------
 functions/rc.sh |  25 +++++++--
 2 files changed, 113 insertions(+), 69 deletions(-)

diff --git a/functions.sh b/functions.sh
index 641deb6..43ea385 100644
--- a/functions.sh
+++ b/functions.sh
@@ -1,6 +1,6 @@
 # Copyright 1999-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
-# shellcheck shell=sh disable=2209,3043
+# shellcheck shell=sh disable=2209,3013,3043
 
 # This file contains a series of function declarations followed by some
 # initialisation code. Functions intended for internal use shall be prefixed
@@ -294,17 +294,55 @@ is_anyof()
 
 #
 # Considers one or more pathnames and prints the one having the newest
-# modification time. If at least one parameter is provided, all parameters shall
-# be considered as pathnames to be compared to one another. Otherwise, the
-# pathnames to be compared shall be read from the standard input as
-# NUL-delimited records. If no pathnames are given, or those specified do not
-# exist, the return value shall be greater than 0. In the case that two or more
-# pathnames are candidates, the one having the lexicographically greatest value
-# shall be selected. Pathnames containing newline characters shall be ignored.
+# modification time. If at least one parameter is provided, all parameters
+# shall be considered as pathnames to be compared to one another. Otherwise,
+# the pathnames to be compared shall be read from the standard input as
+# null-terminated records. In the case that two or more pathnames are
+# candidates, whichever was first specified shall take precedence over the
+# other. If no pathnames are given, or those specified do not exist, the return
+# value shall be greater than 0.
+#
+# Pathnames containing <newline> characters shall be handled correctly if
+# conveyed as positional parameters. Otherwise, the behaviour for such
+# pathnames is unspecified. Users of the function are duly expected to refrain
+# from conveying such pathnames for consumption from the standard input; for
+# example, by specifying a predicate of ! -path $'*\n*' to the find utility.
+# This constraint is expected to be eliminated by a future amendment to the
+# function, once support for read -d becomes sufficiently widespread.
+#
+# The test utility is required to support the -nt primary, per POSIX-1.2024.
+# However, measures are in place to to achieve compatibility with shells that
+# implement the primary without yet fully adhering to the specification.
 #
 newest()
 {
-	_select_by_mtime -r "$@"
+	local path newest
+
+	newest=
+	if [ "$#" -gt 0 ]; then
+		for path; do
+			# The tests within curly braces address a conformance
+			# issue whereby [ existent -nt nonexistent ] is
+			# incorrectly false. As of August 2024, busybox ash,
+			# dash, FreeBSD sh and NetBSD sh are known to be
+			# non-conforming in this respect.
+			if { [ ! "${newest}" ] && [ -e "${path}" ]; } || [ "${path}" -nt "${newest}" ]; then
+				newest=$path
+			fi
+		done
+		test "${newest}" && printf '%s\n' "${newest}"
+	else
+		# Support for read -d '' is not yet sufficiently widespread.
+		tr '\0' '\n' |
+		{
+		while IFS= read -r path; do
+			if { [ ! "${newest}" ] && [ -e "${path}" ]; } || [ "${path}" -nt "${newest}" ]; then
+				newest=$path
+			fi
+		done
+		test "${newest}" && printf '%s\n' "${newest}"
+		}
+	fi
 }
 
 #
@@ -330,17 +368,55 @@ get_nprocs()
 
 #
 # Considers one or more pathnames and prints the one having the oldest
-# modification time. If at least one parameter is provided, all parameters shall
-# be considered as pathnames to be compared to one another. Otherwise, the
-# pathnames to be compared shall be read from the standard input as
-# NUL-delimited records. If no pathnames are given, or those specified do not
-# exist, the return value shall be greater than 0. In the case that two or more
-# pathnames are candidates, the one having the lexicographically lesser value
-# shall be selected. Pathnames containing newline characters shall be ignored.
+# modification time. If at least one parameter is provided, all parameters
+# shall be considered as pathnames to be compared to one another. Otherwise,
+# the pathnames to be compared shall be read from the standard input as
+# null-terminated records. In the case that two or more pathnames are
+# candidates, whichever was first specified shall take precedence over the
+# other. If no pathnames are given, or those specified do not exist, the return
+# value shall be greater than 0.
+#
+# Pathnames containing <newline> characters shall be handled correctly if
+# conveyed as positional parameters. Otherwise, the behaviour for such
+# pathnames is unspecified. Users of the function are duly expected to refrain
+# from conveying such pathnames for consumption from the standard input; for
+# example, by specifying a predicate of ! -path $'*\n*' to the find utility.
+# This constraint is expected to be eliminated by a future amendment to the
+# function, once support for read -d becomes sufficiently widespread.
+#
+# The test utility is required to support the -ot primary, per POSIX-1.2024.
 #
 oldest()
 {
-	_select_by_mtime -- "$@"
+	local path oldest
+
+	oldest=
+	if [ "$#" -gt 0 ]; then
+		for path; do
+			# The specification has [ nonexistent -ot existent ] as
+			# being true. Such is a nuisance in this case but the
+			# preceding tests suffice as a workaround.
+			if [ ! -e "${path}" ]; then
+				continue
+			elif [ ! "${oldest}" ] || [ "${path}" -ot "${oldest}" ]; then
+				oldest=$path
+			fi
+		done
+		test "${oldest}" && printf '%s\n' "${oldest}"
+	else
+		# Support for read -d '' is not yet sufficiently widespread.
+		tr '\0' '\n' |
+		{
+		while IFS= read -r path; do
+			if [ ! -e "${path}" ]; then
+				continue
+			elif [ ! "${oldest}" ] || [ "${path}" -ot "${oldest}" ]; then
+				oldest=$path
+			fi
+		done
+		test "${oldest}" && printf '%s\n' "${oldest}"
+		}
+	fi
 }
 
 #
@@ -675,34 +751,6 @@ whenceforth()
 
 #------------------------------------------------------------------------------#
 
-#
-# See the definitions of _select_by_mtime() and is_older_than(). This function
-# requires that GNU findutils >=4.9 be installed.
-#
-_find0()
-{
-	# Store the name of the GNU find binary, which may be "gfind".
-	hash gfind 2>/dev/null && genfun_bin_find=gfind || genfun_bin_find=find
-
-	_find0()
-	{
-		local opt
-
-		case $1 in
-			-[HL])
-				opt=$1
-				shift
-				set -- "${opt}" -files0-from - "$@"
-				;;
-			*)
-				set -- -files0-from - "$@"
-		esac
-		"${genfun_bin_find}" "$@"
-	}
-
-	_find0 "$@"
-}
-
 #
 # Determines whether the terminal is a dumb one.
 #
@@ -734,25 +782,6 @@ if [ "${BASH_VERSINFO-0}" -ge 5 ]; then
 	'
 fi
 
-#
-# See the definitions of oldest() and newest().
-#
-_select_by_mtime()
-{
-	local sort_opt
-
-	sort_opt=$1
-	shift
-	if [ "$#" -gt 0 ]; then
-		printf '%s\0' "$@"
-	else
-		cat
-	fi \
-	| _find0 -maxdepth 0 ! -path "*${genfun_newline}*" -printf '%T+ %p\n' \
-	| sort "${sort_opt}" \
-	| { IFS= read -r line && printf '%s\n' "${line#* }"; }
-}
-
 #
 # Considers the first parameter as a number of centiseconds and determines
 # whether fewer have elapsed since the last occasion on which the function was

diff --git a/functions/rc.sh b/functions/rc.sh
index 0c14035..4eff3c8 100644
--- a/functions/rc.sh
+++ b/functions/rc.sh
@@ -1,6 +1,6 @@
 # Copyright 1999-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
-# shellcheck shell=sh disable=3043
+# shellcheck shell=sh disable=3013,3043
 
 # This file contains alternative implementations for some of the functions and
 # utilities provided by OpenRC. Please refer to ../functions.sh for coding
@@ -205,9 +205,13 @@ get_bootparam()
 # Takes the first parameter as a reference file/directory then determines
 # whether any of the following parameters refer to newer files/directories.
 #
+# The test utility is required to support the -nt primary, per POSIX-1.2024.
+# However, measures are in place to to achieve compatibility with shells that
+# implement the primary without yet fully adhering to the specification.
+#
 is_older_than()
 {
-	local ref
+	local path ref
 
 	if [ "$#" -eq 0 ]; then
 		warn "is_older_than: too few arguments (got $#, expected at least 1)"
@@ -218,9 +222,20 @@ is_older_than()
 		ref=
 	fi
 	shift
-	{ test "$#" -gt 0 && printf '%s\0' "$@"; } \
-	| _find0 -L ${ref:+-newermm} ${ref:+"${ref}"} -printf '\n' -quit \
-	| read -r _
+	for path; do
+		# The first branch addresses a conformance issue whereby
+		# [ existent -nt nonexistent ] is incorrectly false. As of
+		# August 2024, busybox ash, dash, FreeBSD sh and NetBSD sh are
+		# known to be non-conforming in this respect.
+		if [ ! "${ref}" ] && [ -e "${path}" ]; then
+			return
+		elif [ "${path}" -nt "${ref}" ]; then
+			return
+		elif [ -d "${path}" ] && is_older_than "${ref}" "${path}"/*; then
+			return
+		fi
+	done
+	false
 }
 
 #


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2024-08-11 10:11 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-02 23:14 [gentoo-commits] proj/gentoo-functions:master commit in: functions/, / Sam James
  -- strict thread matches above, loose matches on Subject: below --
2024-08-11 10:11 Sam James
2024-08-11 10:11 Sam James
2024-06-25  4:06 Sam James

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox