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
 }
 
 #

Reply via email to