From 83c57e10c81650bb980e942622973e8a7bb8cbba Mon Sep 17 00:00:00 2001
From: Lucas Holt <luke@foolishgames.com>
Date: Tue, 3 Dec 2024 13:15:31 -0500
Subject: [PATCH] add support for alternate working directory, similar to GNU
 version.

Based on freebsd 14 stable cahnge from FreeBSD
---
 usr.bin/env/Makefile.depend   |  1 +
 usr.bin/env/env.1             | 15 +++++++++++++--
 usr.bin/env/env.c             | 24 ++++++++++++++++++------
 usr.bin/env/tests/env_test.sh | 25 ++++++++++++++++++++++++-
 4 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/usr.bin/env/Makefile.depend b/usr.bin/env/Makefile.depend
index 84b8ddd67e..ffc295f2fe 100644
--- a/usr.bin/env/Makefile.depend
+++ b/usr.bin/env/Makefile.depend
@@ -7,6 +7,7 @@ DIRDEPS = \
 	lib/${CSU_DIR} \
 	lib/libc \
 	lib/libcompiler_rt \
+	lib/libutil \
 
 
 .include <dirdeps.mk>
diff --git a/usr.bin/env/env.1 b/usr.bin/env/env.1
index 6858a6465b..4badea7161 100644
--- a/usr.bin/env/env.1
+++ b/usr.bin/env/env.1
@@ -30,7 +30,7 @@
 .\" From @(#)printenv.1	8.1 (Berkeley) 6/6/93
 .\" From FreeBSD: src/usr.bin/printenv/printenv.1,v 1.17 2002/11/26 17:33:35 ru Exp
 .\"
-.Dd October 7, 2024
+.Dd October 8, 2024
 .Dt ENV 1
 .Os
 .Sh NAME
@@ -44,6 +44,7 @@
 .Op Ar name Ns = Ns Ar value ...
 .Nm
 .Op Fl iv
+.Op Fl C Ar altwd
 .Op Fl L Ns | Ns Fl U Ar user Ns Op / Ns Ar class
 .Op Fl P Ar altpath
 .Op Fl S Ar string
@@ -81,6 +82,12 @@ The environment inherited
 by
 .Nm
 is ignored completely.
+.\"     -C
+.It Fl C Ar altwd
+Change to the specified alternate working directory before executing
+the specified
+.Ar utility
+program.
 .\"	-L | -U
 .It Fl L | Fl U Ar user Ns Op / Ns Ar class
 Add the environment variable definitions from
@@ -508,7 +515,7 @@ The
 utility conforms to
 .St -p1003.1-2001 .
 The
-.Fl 0 , L , P , S , U , u
+.Fl 0 , C , L , P , S , U , u
 and
 .Fl v
 options are non-standard extensions supported by
@@ -531,6 +538,10 @@ and
 .Fl U
 options were added in
 .Fx 13.0 .
+The
+.Fl C
+option was added in
+.Fx 14.2 .
 .Sh BUGS
 The
 .Nm
diff --git a/usr.bin/env/env.c b/usr.bin/env/env.c
index 3b7627e534..724a17d8be 100644
--- a/usr.bin/env/env.c
+++ b/usr.bin/env/env.c
@@ -60,7 +60,7 @@ extern char **environ;
 
 int	 env_verbosity;
 
-static void usage(void);
+static void usage(void) __dead2;
 
 /*
  * Exit codes.
@@ -72,7 +72,7 @@ static void usage(void);
 int
 main(int argc, char **argv)
 {
-	char *altpath, **ep, *p, **parg, term;
+	char *altpath, *altwd, **ep, *p, **parg, term;
 	char *cleanenv[1];
 	char *login_class, *login_name;
 	struct passwd *pw;
@@ -83,6 +83,7 @@ main(int argc, char **argv)
 	int rtrn;
 
 	altpath = NULL;
+	altwd = NULL;
 	login_class = NULL;
 	login_name = NULL;
 	pw = NULL;
@@ -90,7 +91,7 @@ main(int argc, char **argv)
 	login_as_user = false;
 	want_clear = 0;
 	term = '\n';
-	while ((ch = getopt(argc, argv, "-0iL:P:S:U:u:v")) != -1)
+	while ((ch = getopt(argc, argv, "-0C:iL:P:S:U:u:v")) != -1)
 		switch(ch) {
 		case '-':
 		case 'i':
@@ -99,6 +100,9 @@ main(int argc, char **argv)
 		case '0':
 			term = '\0';
 			break;
+		case 'C':
+			altwd = optarg;
+			break;
 		case 'U':
 			login_as_user = true;
 			/* FALLTHROUGH */
@@ -106,7 +110,7 @@ main(int argc, char **argv)
 			login_name = optarg;
 			break;
 		case 'P':
-			altpath = strdup(optarg);
+			altpath = optarg;
 			break;
 		case 'S':
 			/*
@@ -199,6 +203,9 @@ main(int argc, char **argv)
 	if (*argv) {
 		if (term == '\0')
 			errx(EXIT_CANCELED, "cannot specify command with -0");
+		if (altwd && chdir(altwd) != 0)
+			err(EXIT_CANCELED, "cannot change directory to '%s'",
+			    altwd);
 		if (altpath)
 			search_paths(altpath, argv);
 		if (env_verbosity) {
@@ -212,6 +219,11 @@ main(int argc, char **argv)
 		execvp(*argv, argv);
 		err(errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE,
 		    "%s", *argv);
+	} else {
+		if (altwd)
+			errx(EXIT_CANCELED, "must specify command with -C");
+		if (altpath)
+			errx(EXIT_CANCELED, "must specify command with -P");
 	}
 	for (ep = environ; *ep; ep++)
 		(void)printf("%s%c", *ep, term);
@@ -224,7 +236,7 @@ static void
 usage(void)
 {
 	(void)fprintf(stderr,
-	    "usage: env [-0iv] [-L|-U user[/class]] [-P utilpath] [-S string] [-u name]\n"
-	    "           [name=value ...] [utility [argument ...]]\n");
+	    "usage: env [-0iv] [-C workdir] [-L|-U user[/class]] [-P utilpath] [-S string]\n"
+	    "           [-u name] [name=value ...] [utility [argument ...]]\n");
 	exit(1);
 }
diff --git a/usr.bin/env/tests/env_test.sh b/usr.bin/env/tests/env_test.sh
index a3e05c0502..2dc8f1a4c9 100644
--- a/usr.bin/env/tests/env_test.sh
+++ b/usr.bin/env/tests/env_test.sh
@@ -83,6 +83,8 @@ altpath_body()
 {
 	echo "echo ${magic_words}" >magic_words
 	chmod 0755 magic_words
+	atf_check -s exit:125 -e match:"must specify command" \
+		  env -P "${PWD}"
 	atf_check -s exit:127 -e match:"No such file" \
 		  env magic_words
 	atf_check -o inline:"${magic_words}\n" \
@@ -100,7 +102,7 @@ equal_body()
 	chmod 0755 "magic=words"
 	atf_check -o match:"^${PWD}/magic=words$" \
 		  env "${PWD}/magic=words"
-	atf_check -o match:"^magic=words$" \
+	atf_check -s exit:125 -e match:"must specify command" \
 		  env -P "${PATH}:${PWD}" "magic=words"
 	atf_check -o inline:"${magic_words}\n" \
 		  env command "${PWD}/magic=words"
@@ -108,6 +110,26 @@ equal_body()
 		  env PATH="${PATH}:${PWD}" command "magic=words"
 }
 
+atf_test_case chdir
+chdir_head()
+{
+	atf_set "descr" "Change working directory"
+}
+chdir_body()
+{
+	local subdir="dir.$$"
+	atf_check -o inline:"${PWD}\n" \
+		  env pwd
+	atf_check -s exit:125 -e match:"must specify command" \
+		  env -C "${subdir}"
+	atf_check -s exit:125 \
+		  -e match:"cannot change directory to '${subdir}':" \
+		  env -C "${subdir}" pwd
+	atf_check mkdir "${subdir}"
+	atf_check -o inline:"${PWD}/${subdir}\n" \
+		  env -C "${subdir}" pwd
+}
+
 atf_test_case stdout
 stdout_head()
 {
@@ -133,5 +155,6 @@ atf_init_test_cases()
 	atf_add_test_case false
 	atf_add_test_case altpath
 	atf_add_test_case equal
+	atf_add_test_case chdir
 	atf_add_test_case stdout
 }