[PATCH] Allow qualified filename (bar/foo) PATH searching.

<[email protected]>
Newsgroups gmane.os.netbsd.devel.userlevel
Message-ID <[email protected]>
POSIX requires that PATH searching be done only if the identifier
(aka "file") has no '/' (if it has, it is a "path").
    
Allow searching qualified filenames, i.e. Resource Identifier,
like dk/ctl, vect/in/dxf by setting an environment variable
PATH_SEARCH_OPT with the setting 'Q' in the definition.
    
It works in all the three shells (sh(1), ksh(1) and csh(1)) as
well as with all the utilities using exec[lv]p or posit_spawnp (also
then system(3)).
    
A man page pathsearch(7) is added explaining this all.

The diff is attached and this can be also retrieved as:
commit 502cb391d594a2722cca46e135f79c02516981d5

with https://github.com/tlaronde/BeSiDe

diff --git a/include/pathsearch.h b/include/pathsearch.h
new file mode 100644
index 000000000000..59df79021843
--- /dev/null
+++ b/include/pathsearch.h
@@ -0,0 +1,34 @@
+/*
+ * Written by Thierry Laronde <[email protected]> 2024-09
+ * Public domain.
+ * See pathsearch(7).
+ */
+
+#ifndef _PATHSEARCH_H_
+#define	_PATHSEARCH_H_
+
+#include <string.h>
+
+/*
+ * For the feature testing macros, the parameters are environment
+ * variable values of the same but upper case name.
+ */
+#define PATH_SEARCH_OPT_QFILENAME 'q'
+#define PATH_SEARCH_OPT_QFILENAME_FORCED 'Q'
+#define	PATH_SEARCH_QFILENAME_ON(path_search_opt) \
+( (path_search_opt) != NULL \
+	&& strchr(path_search_opt, PATH_SEARCH_OPT_QFILENAME_FORCED) != NULL )
+
+/*
+ * A qualified filename should be considered a Resource Identifier
+ * to be contrasted to a Locator (where a resource lies in the
+ * namespace). See pathsearch(7).
+ */
+#define PATH_SEARCH_IS_QFILENAME(name, name_len) \
+	( (name) != NULL && (name_len) != 0 \
+		&& (name)[0] != '/' && strstr(name, "./") == NULL \
+		&& (name)[(name_len)-1] != '/' \
+		&& (name)[(name_len)-1] != '.' )
+
+#endif /* !_PATHSEARCH_H_ */
+
diff --git a/share/man/man7/pathsearch.7 b/share/man/man7/pathsearch.7
new file mode 100644
index 000000000000..6ecc313e772b
--- /dev/null
+++ b/share/man/man7/pathsearch.7
@@ -0,0 +1,234 @@
+.\"
+.\" Public Domain.
+.\"
+.Dd September 29, 2024
+.Dt PATHSEARCH 7
+.Os
+.Sh NAME
+.Nm pathsearch
+.Nd when and how the search of a file to execute is handled.
+.Sh DESCRIPTION
+A program to execute is given to a shell, to one of the
+.Xr exec 3
+or 
+.Xr posix_spawn 3
+family of functions in two forms: either as a
+.Em locator
+specifying exactly (even if implicitely) where the file is, or as an
+.Em identifier
+to be searched in order to be located. (POSIX generally use
+.Em path
+as a mean to imply a locator and
+.Em file
+to imply an identifier.)
+.Pp
+The shells
+.Xr sh 1 ,
+.Xr ksh 1 ,
+.Xr csh 1 ,
+as well as the variants of the
+.Xr execlp 3
+and
+.Xr execvp 3
+functions, and the
+.Xr posix_spawnp 3
+function (used by the implementation of
+.Xr system 3 )
+handle both locators and identifiers. The other variants of
+.Xr exec 3
+or
+.Xr posix_spawn 3
+expect only locators and do no path searching.
+.Pp
+For the path searching, two environment variables drive the searching:
+.Em PATH
+and
+.Em PATH_SEARCH_OPT .
+.Pp
+In order for the searching to be done, the command given as argument
+has to be identified as an identifier, and not as a locator.
+.Pp
+The traditional POSIX rule for this is simple: when path searching is
+to be attempted, if the command name given does not contain a
+.Ql \&/ ,
+it is considered an identifier and is then searched. Otherwise, it is a
+locator and no search is done.
+.Pp
+Hence, with the traditional behavior, given a command as 
+.Qq bar/foo ,
+no path searching would be done and this will be tried in the current
+working directory.
+.Pp
+The rules concerning what is an identifier can be changed using the
+.Em PATH_SEARCH_OPT
+variable. See below
+.Sx Extending identifiers to qualified filenames .
+.Pp
+When the argument is considered an identifier (according to whatever
+rule), it is not searched everywhere but only in the directories
+specified in the
+.Em PATH
+environment variable.
+.Pp
+The
+.Em PATH
+is a sequence of colon
+.Ql \&:
+separated directory pathnames to be tried, in turn, for locating the
+identified resource. Initially, it is set to
+.Pp
+.Dl /usr/bin:/bin:/usr/pkg/bin:/usr/local/bin
+by
+.Xr login 1 .
+It can be redefined later.
+.Pp
+An empty dir specification (that can be expressed in PATH by a leading
+or a trailing colon, or by two consecutive colons) means the current
+working directory. This is considered a security risk since, by the very
+nature of the current working, what will be executed depends on the
+context.  It is thus highly recommended to verify that an empty dir
+alternative has not slipped by mistake in the PATH definition.
+.Pp
+The
+.Xr sh 1
+syntax allows for example to interpolate a dir specification via a
+variable, but offers means to protect the PATH from having by mistake
+a current working dir definition because the variable is not defined
+or empty, in this way:
+.Pp
+.Dl PATH="/bin:/usr/bin:${MYSCRIPTS:+${MYSCRIPTS}:}/usr/pkg/bin"
+.Pp
+or
+.Pp
+.Dl PATH="/bin:/usr/bin${MYSCRIPTS:+:${MYSCRIPTS}}:/usr/pkg/bin"
+.Pp
+The trick here is that the variable is only expanded if it is
+defined and not empty, and, in this case only, a
+.Ql \&:
+colon is added to its definition. The result being that if the
+.Em MYSCRIPTS
+variable were not defined or empty, this will not result in two
+consecutive colons, adding by mistake the current working directory as
+an alternative dir to search in. See
+.Xr sh 1
+for an explanation of the syntax.
+.Ss Extending identifiers to qualified filenames
+The environment variable
+.Em PATH_SEARCH_OPT
+can be defined to change the path searching.
+.Pp
+The definition of the PATH_SEARCH_OPT is a string of ASCII letters
+(the options are case sensitive).
+.Pp
+At the moment, only one option is defined:
+.Bl -tag -width "Q"
+.It Q
+The rule identifying an identifier is changed to allow qualified
+filenames like
+.Qq bar/foo
+to be searched for with the PATH. A
+.Em qualified filename
+is a Resource Identifier (not a locator) consisting of a sequence
+of one or more components separated by
+.Ql / ,
+the sequence not starting or ending by a
+.Ql /
+and any component not ending by a
+.Ql \&. .
+Thus
+.Qq fs/create ,
+.Qq fs/ck ,
+.Qq fs/mount ,
+.Qq fs/ctl
+are all qualified filenames, as well as
+.Qq texlive/latex
+or
+.Qq kertex/latex ,
+as well as (G.R.A.S.S. example)
+.Qq vect/in/dxf .
+But 
+.Qq /vect/in/dxf
+is not an identifier but a locator, as well as
+.Qq ./vect/in/dxf
+or
+.Qq vect/in/dxf.
+in this latter case because of the trailing dot of the last component.
+.El
+.Sh IMPLEMENTATION NOTES
+Since the behavior can not be changed only at a shell level, there
+has to be a mean to inform both the shell and the libc to change the
+behavior. Hence the
+.Em PATH_SEARCH_OPT
+environment variable. And since it would be suboptimal to create
+another variable when something has to be optionally changed in the way
+the path is searched, the variable has to have a definition.
+.Pp
+The upper case was chosen because, initially, there was a lower case
+variant meaning: do qualified filename searching if not
+.Em POSIXLY_CORRECT .
+But if this extended behavior is not, strictly speaking, POSIX
+compliant, it is not a violation of the spirit: a qualified filename
+is an identifier, not a locator that is a path wandering instruction; a
+qualified filename can not escape the directory. In this sense, and
+without judging the relevance of the notion of a
+.Qq restricted shell ,
+as is expressed, for example, in
+.Xr ksh 1 ,
+a qualified filename is neither absolute nor relative, so is
+compatible with the notion. So the lower case variant was dropped, but
+the principle of distinguishing between lower case (conditional) and
+upper case (mandatory) is retained for possible further extensions.
+.Pp
+The new rule was designed so it can match the meaning (an identifier
+not a locator), be compatible with a Unix filesystem hierarchy, and
+easy to test. The restriction of no ending
+.Ql \&.
+was initially for the simplicity and thus efficiency of testing. But
+it makes some sense. 
+.Ql \&.
+and
+.Qq \&.\&.
+and not identifiers (they are not uniq and do not exist as names in
+any filesystem) but are
+.Qq pronoun
+locators. They end with a dot. So any sequence ending with a dot can
+be reserved for the group of pronoun locators allowing extensions
+of the notion.
+.Pp
+The fact that, when extended filenames search is requested, a command
+passed as
+.Qq bar/foo
+will be searched and not executed in the current directory, thus
+imposing, if it was actually the intention to do so, to specify it as
+.Qq ./bar/foo
+is not considered a slight inconvenient but an improvement.
+.Sh SEE ALSO
+.Xr environ 7 ,
+.Xr exec 3 ,
+.Xr posix_spawn 3 ,
+.Xr sh 1 ,
+.Xr ksh 1 ,
+.Xr csh 1 .
+.Sh SOURCES
+.Pa include/pathsearch.h
+.Pp lib/libc/gen/execvp.c
+.Pp lib/libc/gen/posix_spawnp.c
+.Pp bin/sh/exec.c
+.Pp bin/ksh/exec.c
+.Pp bin/csh/exec.c
+.Sh HISTORY
+Allowing qualified filenames for commands to be searched (not exactly
+with the same rules) was first encountered by the author in the
+.Em Plan9
+system.
+.Sh AUTHOR
+.An "Thierry Laronde" Aq Mt [email protected] .
+.Pp
+The
+.Xr sh 1
+way of defining the
+.Ev PATH
+with interpolated variables, protecting from undefined ones, comes
+from
+.An "Robert Elz" Aq Mt [email protected] .
+
diff --git a/bin/csh/exec.c b/bin/csh/exec.c
index bd06892a1c92..b45e1d3d5222 100644
--- a/bin/csh/exec.c
+++ b/bin/csh/exec.c
@@ -45,6 +45,7 @@ __RCSID("$NetBSD: exec.c,v 1.33 2019/01/05 16:54:00 christos Exp $");
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <pathsearch.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
@@ -55,7 +56,8 @@ __RCSID("$NetBSD: exec.c,v 1.33 2019/01/05 16:54:00 christos Exp $");
 
 /*
  * System level search and execute of a command.  We look in each directory
- * for the specified command name.  If the name contains a '/' then we
+ * for the specified command name.  If the name contains a '/' and
+ * qualified filenames is not on (see pathsearch.7) then we
  * execute only the full path name.  If there is no search path then we
  * execute only full path names.
  */
@@ -194,13 +196,26 @@ doexec(Char **v, struct command *t)
     sigemptyset(&nsigset);
     (void)sigprocmask(SIG_SETMASK, &nsigset, NULL);
     /*
-     * If no path, no words in path, or a / in the filename then restrict the
-     * command search.
+     * If no path, no words in path, or a / in the filename but not
+     * doing qualified filename, then restrict the command search.
      */
-    if (pathv == 0 || pathv->vec[0] == 0 || slash)
+    {
+    const char *path_search_opt;
+    size_t ln;
+    int do_qfilename;
+
+    ln = strlen(short2str(*av));
+
+    path_search_opt = getenv("PATH_SEARCH_OPT");
+    do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+	&& PATH_SEARCH_IS_QFILENAME(short2str(*av), ln);
+
+    if (pathv == 0 || pathv->vec[0] == 0
+	|| (do_qfilename == 0 && slash) )
 	pv = justabs;
     else
 	pv = pathv->vec;
+    }
     sav = Strspl(STRslash, *av); 	/* / command name for postpending */
     Vsav = sav;
     if (havhash)
@@ -510,12 +525,21 @@ iscommand(Char *name)
     Char **pv, *sav;
     int hashval, hashval1, i;
     int slash;
+    const char *path_search_opt;
+    size_t ln;
+    int do_qfilename;
 
     hashval = 0;
     slash = any(short2str(name), '/');
     v = adrof(STRpath);
     
-    if (v == 0 || v->vec[0] == 0 || slash)
+    ln = strlen(short2str(name));
+
+    path_search_opt = getenv("PATH_SEARCH_OPT");
+    do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(short2str(name), ln);
+
+    if (v == 0 || v->vec[0] == 0 || (do_qfilename == 0 && slash) )
 	pv = justabs;
     else
 	pv = v->vec;
@@ -700,10 +724,20 @@ tellmewhat(struct wordent *lexp, Char *str)
     if ((i = iscommand(sp->word)) != 0) {
 	Char **pv;
 	struct varent *v;
+	const char *path_search_opt;
+	size_t ln;
+	int do_qfilename;
 	int    slash = any(short2str(sp->word), '/');
 
+	ln = strlen(short2str(sp->word));
+
+	path_search_opt = getenv("PATH_SEARCH_OPT");
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(short2str(sp->word), ln);
+
+
 	v = adrof(STRpath);
-	if (v == 0 || v->vec[0] == 0 || slash)
+	if (v == 0 || v->vec[0] == 0 || (do_qfilename == 0 && slash) )
 	    pv = justabs;
 	else
 	    pv = v->vec;
diff --git a/bin/ksh/exec.c b/bin/ksh/exec.c
index 721f301f393f..0bf7d6e0cecd 100644
--- a/bin/ksh/exec.c
+++ b/bin/ksh/exec.c
@@ -11,6 +11,7 @@ __RCSID("$NetBSD: exec.c,v 1.28 2018/06/03 12:18:29 kamil Exp $");
 
 #include <sys/stat.h>
 #include <ctype.h>
+#include <pathsearch.h>
 #include <stdbool.h>
 
 #include "sh.h"
@@ -575,7 +576,17 @@ comexec(t, tp, ap, flags)
 		rv = subst_exstat;
 		goto Leave;
 	} else if (!tp) {
-		if (Flag(FRESTRICTED) && ksh_strchr_dirsep(cp)) {
+		const char *path_search_opt;
+		int ln;
+		int do_qfilename;
+
+		ln = strlen(cp);
+		path_search_opt = str_val(global("PATH_SEARCH_OPT"));
+		do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+			&& PATH_SEARCH_IS_QFILENAME(cp, ln);
+
+		if (Flag(FRESTRICTED) && do_qfilename == 0
+			&& ksh_strchr_dirsep(cp)) {
 			warningf(true, "%s: restricted", cp);
 			rv = 1;
 			goto Leave;
@@ -899,8 +910,16 @@ findcom(name, flags)
 	int insert = Flag(FTRACKALL);	/* insert if not found */
 	char *fpath;			/* for function autoloading */
 	char *npath;
+	const char *path_search_opt;
+	size_t ln;
+	int do_qfilename;
+
+ 	ln = strlen(name);
+	path_search_opt = str_val(global("PATH_SEARCH_OPT"));
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(name, ln);
 
-	if (ksh_strchr_dirsep(name) != NULL) {
+	if (do_qfilename == 0 && ksh_strchr_dirsep(name) != NULL) {
 		insert = 0;
 		/* prevent FPATH search below */
 		flags &= ~FC_FUNC;
@@ -1050,18 +1069,26 @@ search(name, pathx, mode, errnop)
 	const char *sp, *p;
 	char *xp;
 	XString xs;
+	const char *path_search_opt;
 	int namelen;
+	int do_qfilename;
 
 	if (errnop)
 		*errnop = 0;
 
-	if (ksh_strchr_dirsep(name)) {
+	namelen = strlen(name);
+
+	path_search_opt = str_val(global("PATH_SEARCH_OPT"));
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(name, namelen);
+
+	if (do_qfilename == 0 && ksh_strchr_dirsep(name)) {
 		if (search_access(name, mode, errnop) == 0)
 			return (char *)__UNCONST(name);
 		return NULL;
 	}
 
-	namelen = strlen(name) + 1;
+	++namelen;	/* to include terminal nul */
 	Xinit(xs, xp, 128, ATEMP);
 
 	sp = pathx;
diff --git a/bin/sh/exec.c b/bin/sh/exec.c
index 4b1c5885371c..09dc0cd09a66 100644
--- a/bin/sh/exec.c
+++ b/bin/sh/exec.c
@@ -47,6 +47,7 @@ __RCSID("$NetBSD: exec.c,v 1.59 2024/07/12 07:30:30 kre Exp $");
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <pathsearch.h>	/* PATH_SEARCH_OPT */
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -125,12 +126,26 @@ void
 shellexec(char **argv, char **envp, const char *path, int idx, int vforked)
 {
 	char *cmdname;
+#ifndef SMALL
+	const char *path_search_opt;
+	size_t ln;
+	int do_qfilename;
+#endif
 	int e, action;
 	struct stat statb;
 
 	action = E_EXEC;
 
+#ifndef SMALL
+ 	ln = strlen(argv[0]);
+	path_search_opt = lookupvar("PATH_SEARCH_OPT");
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(argv[0], ln);
+
+	if (do_qfilename == 0 && strchr(argv[0], '/') != NULL) {
+#else
 	if (strchr(argv[0], '/') != NULL) {
+#endif
 		tryexec(argv[0], argv, envp, vforked);
 		e = errno;
 		if (e == EACCES && stat(argv[0], &statb) == -1)
@@ -574,9 +589,27 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
 	struct stat statb;
 	int e;
 	int (*bltin)(int,char **);
+#ifndef SMALL
+	const char *path_search_opt;
+	size_t ln;
+	int do_qfilename;
+#endif
 
+#ifndef SMALL
+ 	ln = strlen(name);
+	path_search_opt = lookupvar("PATH_SEARCH_OPT");
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(name, ln);
+
+	/*
+	 * If not searching for a qualified filename, then if the name
+	 * contains a slash, don't use PATH or hash table.
+	 */
+	if (do_qfilename == 0 && strchr(name, '/') != NULL) {
+#else
 	/* If name contains a slash, don't use PATH or hash table */
 	if (strchr(name, '/') != NULL) {
+#endif
 		if (act & DO_ABS) {
 			while (stat(name, &statb) < 0) {
 #ifdef SYSV
diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index a2274fb94a4b..021b9d2eea1f 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -3191,6 +3191,7 @@
 ./usr/include/openssl/x509v3err.h		comp-c-include
 ./usr/include/panel.h				comp-c-include
 ./usr/include/paths.h				comp-c-include
+./usr/include/pathsearch.h			comp-c-include
 ./usr/include/pcap-namedb.h			comp-c-include
 ./usr/include/pcap.h				comp-c-include
 ./usr/include/pcap/bpf.h			comp-c-include
diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi
index e1d4e9c3e2f1..3ddc26880564 100644
--- a/distrib/sets/lists/man/mi
+++ b/distrib/sets/lists/man/mi
@@ -6148,6 +6148,7 @@
 ./usr/share/man/man7/openssl-threads.7		man-crypto-man		.man,openssl=30
 ./usr/share/man/man7/orders.7			man-reference-man	.man
 ./usr/share/man/man7/packages.7			man-obsolete		obsolete
+./usr/share/man/man7/pathsearch.7		man-reference-man
 ./usr/share/man/man7/pcap-filter.7		man-netutil-man		.man
 ./usr/share/man/man7/pcap-linktype.7		man-netutil-man		.man
 ./usr/share/man/man7/pcap-tstamp.7		man-netutil-man		.man
diff --git a/distrib/sets/lists/manhtml/mi b/distrib/sets/lists/manhtml/mi
index 0427facdeab2..9c1d76b19182 100644
--- a/distrib/sets/lists/manhtml/mi
+++ b/distrib/sets/lists/manhtml/mi
@@ -2370,6 +2370,7 @@
 ./usr/share/man/html7/openssl-glossary.html	man-crypto-htmlman		html,openssl=30
 ./usr/share/man/html7/openssl-threads.html	man-crypto-htmlman		html,openssl=30
 ./usr/share/man/html7/orders.html		man-reference-htmlman	html
+./usr/share/man/html7/pathsearch.html		man-reference-htmlman	html
 ./usr/share/man/html7/pcap-filter.html		man-netutil-htmlman	html
 ./usr/share/man/html7/pcap-linktype.html	man-netutil-htmlman	html
 ./usr/share/man/html7/pcap-tstamp.html		man-netutil-htmlman	html
diff --git a/include/Makefile b/include/Makefile
index f7e000fb2cb2..1ac92f38c095 100644
--- a/include/Makefile
+++ b/include/Makefile
@@ -16,7 +16,7 @@ INCS=	a.out.h aio.h ar.h assert.h atomic.h \
 	login_cap.h lwp.h malloc.h math.h md2.h \
 	memory.h mntopts.h monetary.h mpool.h mqueue.h \
 	ndbm.h netconfig.h netdb.h netgroup.h nlist.h nl_types.h nsswitch.h \
-	paths.h pwd.h quota.h randomid.h ranlib.h re_comp.h regex.h regexp.h \
+	paths.h pathsearch.h pwd.h quota.h randomid.h ranlib.h re_comp.h regex.h regexp.h \
 	resolv.h res_update.h rmt.h sched.h search.h semaphore.h setjmp.h \
 	string.h sgtty.h signal.h spawn.h stab.h stddef.h stdio.h \
 	stdlib.h stdnoreturn.h strings.h stringlist.h struct.h sysexits.h \
diff --git a/lib/libc/gen/execvp.c b/lib/libc/gen/execvp.c
index d29e2a0fc6d2..3219a99e7796 100644
--- a/lib/libc/gen/execvp.c
+++ b/lib/libc/gen/execvp.c
@@ -47,6 +47,7 @@ __RCSID("$NetBSD: execvp.c,v 1.32 2024/01/20 14:52:47 christos Exp $");
 #include <limits.h>
 #include <unistd.h>
 #include <paths.h>
+#include <pathsearch.h>
 #include "reentrant.h"
 #include "extern.h"
 
@@ -62,9 +63,11 @@ execvpe(const char *name, char * const *argv, char * const * envp)
 	int cnt;
 	size_t lp, ln;
 	int eacces = 0;
+	int do_qfilename;
 	unsigned int etxtbsy = 0;
 	char buf[PATH_MAX];
 	const char *bp, *path, *p;
+	const char *path_search_opt;
 
 	_DIAGASSERT(name != NULL);
 
@@ -74,8 +77,16 @@ execvpe(const char *name, char * const *argv, char * const * envp)
 		goto done;
 	}
 	ln = strlen(name);
-	/* If it's an absolute or relative path name, it's easy. */
-	if (strchr(name, '/')) {
+
+	path_search_opt = getenv("PATH_SEARCH_OPT");
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(name, ln);
+
+	/* 
+	 * If it's an absolute or relative path name (and not a
+	 * qualified filename and asked to search for it), it's easy.
+	 */
+	if (do_qfilename == 0 && strchr(name, '/')) {
 		bp = name;
 		path = "";
 		goto retry;
diff --git a/lib/libc/gen/posix_spawnp.c b/lib/libc/gen/posix_spawnp.c
index 2474bbe6970c..7e7e20e04313 100644
--- a/lib/libc/gen/posix_spawnp.c
+++ b/lib/libc/gen/posix_spawnp.c
@@ -39,6 +39,7 @@ __RCSID("$NetBSD: posix_spawnp.c,v 1.4 2020/05/11 14:54:34 kre Exp $");
 #include <assert.h>
 #include <errno.h>
 #include <paths.h>
+#include <pathsearch.h>
 #include <spawn.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -53,15 +54,23 @@ int posix_spawnp(pid_t * __restrict pid, const char * __restrict file,
 {
 	char fpath[FILENAME_MAX];
 	const char *path, *p;
+	const char *path_search_opt;
 	size_t lp, ln;
 	int err;
+	int do_qfilename;
 
 	_DIAGASSERT(file != NULL);
 
+	ln = strlen(file);
+	path_search_opt = getenv("PATH_SEARCH_OPT");
+	do_qfilename = PATH_SEARCH_QFILENAME_ON(path_search_opt)
+		&& PATH_SEARCH_IS_QFILENAME(file, ln);
+
 	/*
-	 * If there is a / in the name, fall straight through to posix_spawn().
+	 * If not treating a qualified filename, and there is a / in
+	 *  the name, fall straight through to posix_spawn().
 	 */
-	if (strchr(file, '/') != NULL)
+	if (do_qfilename == 0 && strchr(file, '/') != NULL)
 		return posix_spawn(pid, file, fa, sa, cav, env);
 
 	/* Get the path we're searching. */
@@ -72,7 +81,6 @@ int posix_spawnp(pid_t * __restrict pid, const char * __restrict file,
 	 * Find an executable image with the given name in the PATH
 	 */
 
-	ln = strlen(file);
 	err = 0;
 	do {
 		/* Find the end of this path element. */
diff --git a/share/man/man7/Makefile b/share/man/man7/Makefile
index 9a8ab4202f52..d40457be1d81 100644
--- a/share/man/man7/Makefile
+++ b/share/man/man7/Makefile
@@ -20,6 +20,7 @@ MAN+=	module.7
 MAN+=	nls.7
 MAN+=	operator.7
 MAN+=	orders.7
+MAN+=	pathsearch.7
 MAN+=	pkgsrc.7
 MAN+=	release.7
 MAN+=	rfc6056.7
diff --git a/share/man/man7/environ.7 b/share/man/man7/environ.7
index b1c06f50ed4d..f88dbe818528 100644
--- a/share/man/man7/environ.7
+++ b/share/man/man7/environ.7
@@ -29,7 +29,7 @@
 .\"
 .\"	@(#)environ.7	8.3 (Berkeley) 4/19/94
 .\"
-.Dd January 21, 2011
+.Dd September 29, 2024
 .Dt ENVIRON 7
 .Os
 .Sh NAME
@@ -174,6 +174,9 @@ is set to
 .Pp
 initially by
 .Xr login 1 .
+.It Ev PATH_SEARCH_OPT
+options to change the way path searching is done. Initially unset. See
+.Xr pathsearch 7 .
 .It Ev PRINTER
 The name of the default printer to be used by
 .Xr lpr 1 ,
-- 
        Thierry Laronde <tlaronde +AT+ kergis +dot+ com>
                     http://www.kergis.com/
                    http://kertex.kergis.com/
Key fingerprint = 0FF7 E906 FBAF FE95 FD89  250D 52B1 AE95 6006 F40C
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.