From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <gentoo-commits+bounces-1526739-garchives=archives.gentoo.org@lists.gentoo.org>
Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits))
	(No client certificate requested)
	by finch.gentoo.org (Postfix) with ESMTPS id E080D15812E
	for <garchives@archives.gentoo.org>; Fri,  9 Jun 2023 11:02:29 +0000 (UTC)
Received: from pigeon.gentoo.org (localhost [127.0.0.1])
	by pigeon.gentoo.org (Postfix) with SMTP id 133D8E07F6;
	Fri,  9 Jun 2023 11:02:29 +0000 (UTC)
Received: from smtp.gentoo.org (dev.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256)
	(No client certificate requested)
	by pigeon.gentoo.org (Postfix) with ESMTPS id 8A3ACE07F6
	for <gentoo-commits@lists.gentoo.org>; Fri,  9 Jun 2023 11:02:28 +0000 (UTC)
Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (4096 bits))
	(No client certificate requested)
	by smtp.gentoo.org (Postfix) with ESMTPS id BCAEC340D40
	for <gentoo-commits@lists.gentoo.org>; Fri,  9 Jun 2023 11:02:27 +0000 (UTC)
Received: from localhost.localdomain (localhost [IPv6:::1])
	by oystercatcher.gentoo.org (Postfix) with ESMTP id 3313DA8D
	for <gentoo-commits@lists.gentoo.org>; Fri,  9 Jun 2023 11:02:26 +0000 (UTC)
From: "Sam James" <sam@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Content-Transfer-Encoding: 8bit
Content-type: text/plain; charset=UTF-8
Reply-To: gentoo-dev@lists.gentoo.org, "Sam James" <sam@gentoo.org>
Message-ID: <1686293830.2a78a15de6e5d90da0657bf74929993b1b51a337.sam@gentoo>
Subject: [gentoo-commits] proj/gentoo-functions:master commit in: /
X-VCS-Repository: proj/gentoo-functions
X-VCS-Files: functions.sh test-functions
X-VCS-Directories: /
X-VCS-Committer: sam
X-VCS-Committer-Name: Sam James
X-VCS-Revision: 2a78a15de6e5d90da0657bf74929993b1b51a337
X-VCS-Branch: master
Date: Fri,  9 Jun 2023 11:02:26 +0000 (UTC)
Precedence: bulk
List-Post: <mailto:gentoo-commits@lists.gentoo.org>
List-Help: <mailto:gentoo-commits+help@lists.gentoo.org>
List-Unsubscribe: <mailto:gentoo-commits+unsubscribe@lists.gentoo.org>
List-Subscribe: <mailto:gentoo-commits+subscribe@lists.gentoo.org>
List-Id: Gentoo Linux mail <gentoo-commits.gentoo.org>
X-BeenThere: gentoo-commits@lists.gentoo.org
X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply
X-Archives-Salt: 8ce94ec8-f458-4c97-be42-0855137d0306
X-Archives-Hash: a763453017daa17b9425a133635a0b5a

commit:     2a78a15de6e5d90da0657bf74929993b1b51a337
Author:     Kerin Millar <kfm <AT> plushkava <DOT> net>
AuthorDate: Thu Jun  8 05:48:02 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Jun  9 06:57:10 2023 +0000
URL:        https://gitweb.gentoo.org/proj/gentoo-functions.git/commit/?id=2a78a15d

Add a chdir() function to act as a safer alternative to the cd builtin

To run cd "$dir" is problematic because:

1) it may consider its operand as an option
2) it will search CDPATH for an operand not beginning with ./, ../ or /
3) it will switch to OLDPWD if the operand is -
4) cdable_vars causes bash to treat the operand as a potential varname

This commit introduces a chdir() function that addresses all of these
pitfalls.

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

 functions.sh   | 18 +++++++++++++++++
 test-functions | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/functions.sh b/functions.sh
index 7838c90..154c8a4 100644
--- a/functions.sh
+++ b/functions.sh
@@ -534,6 +534,24 @@ is_int() {
 	esac
 }
 
+#
+#   A safe wrapper for the cd builtin. To run cd "$dir" is problematic because:
+#
+#   1) it may consider its operand as an option
+#   2) it will search CDPATH for an operand not beginning with ./, ../ or /
+#   3) it will switch to OLDPWD if the operand is -
+#   4) cdable_vars causes bash to treat the operand as a potential variable name
+#
+chdir() {
+	if [ "$BASH" ]; then
+		shopt -u cdable_vars
+	fi
+	if [ "$1" = - ]; then
+		set -- ./-
+	fi
+	CDPATH= cd -- "$@"
+}
+
 #
 #   Determine whether the first operand contains any visible characters.
 #

diff --git a/test-functions b/test-functions
index b80587b..5a6b23b 100755
--- a/test-functions
+++ b/test-functions
@@ -14,7 +14,7 @@ bailout() {
 assign_tmpdir() {
 	# shellcheck disable=1007
 	dir=$(mktemp -d) \
-	&& CDPATH= cd -- "${dir}" \
+	&& chdir "${dir}" \
 	|| bailout "Couldn't create or change to the temp dir"
 }
 
@@ -24,6 +24,49 @@ cleanup_tmpdir() {
 	fi
 }
 
+test_chdir() {
+	set -- \
+		1  grandchild  \
+		1         var  \
+		0          -L  \
+		0          -p  \
+		0          -e  \
+		0          -@  \
+		0           -  \
+		0       child
+
+	if ! mkdir -p -- -L -p -e -@ - child child/grandchild; then
+		bailout "Couldn't set up all test directories"
+	fi
+
+	callback() {
+		shift
+		test_description="chdir $(print_args "$@")"
+		if [ "$BASH" ]; then
+			shopt -s cdable_vars
+		fi
+		CDPATH=child var=$CDPATH chdir "$@" \
+		&& test "$PWD" != "$OLDPWD" \
+		&& cd - >/dev/null
+	}
+
+	iterate_tests 2 "$@"
+}
+
+test_chdir_noop() {
+	set -- 0 ''
+
+	callback() {
+		shift
+		test_description="chdir $(print_args "$@")"
+		chdir "$@" \
+		&& test "$PWD" = "$OLDPWD" \
+		|| { cd - >/dev/null; false; }
+	}
+
+	iterate_tests 2 "$@"
+}
+
 test_is_older_than() {
 	set -- \
 		1  N/A           N/A \
@@ -317,12 +360,24 @@ iterate_tests() {
 			fi
 		done
 		eval "${code}"
-		if [ "$?" -eq "$1" ]; then
+		case $? in
+			0)
+				test "$?" -eq "$1"
+				;;
+			*)
+				test "$?" -ge "$1"
+		esac
+		if [ "$?" -eq 0 ]; then
 			passed=$((passed + 1))
 		else
 			printf 'not '
 		fi
-		printf 'ok %d - %s (expecting %d)\n' "${i}" "${test_description}" "$1"
+		if [ "$1" -eq 0 ]; then
+			expected=$1
+		else
+			expected=">=$1"
+		fi
+		printf 'ok %d - %s (expecting %s)\n' "${i}" "${test_description}" "${expected}"
 		shift "${slice_width}"
 	done
 	return "$(( passed < total ))"
@@ -359,6 +414,8 @@ export TEST_GENFUNCS=1
 export TZ=UTC
 
 rc=0
+test_chdir || rc=1
+test_chdir_noop || rc=1
 ( ewarn() { true; }; test_is_older_than ) || rc=1
 test_get_bootparam || rc=1
 test_esyslog || rc=1