From c0a855b36e9872c46d3e89471245923266b68c08 Mon Sep 17 00:00:00 2001
From: Mattias Andrée <maandree@member.fsf.org>
Date: Sun, 20 Dec 2015 18:33:52 +0100
Subject: add searchpath and searchpath2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Mattias Andrée <maandree@member.fsf.org>
---
 include/unistd.h         |  74 ++++++++++++++++++++++++++
 src/unistd/exec.c        |  50 ++----------------
 src/unistd/searchpath.c  |  55 ++++++++++++++++++++
 src/unistd/searchpath2.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 264 insertions(+), 47 deletions(-)
 create mode 100644 src/unistd/searchpath.c
 create mode 100644 src/unistd/searchpath2.c

diff --git a/include/unistd.h b/include/unistd.h
index 47db049..d27f342 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -800,6 +800,80 @@ int fexecv(int, char* const[]);
  */
 int fexecve(int, char* const[], char* const[]);
 
+#if defined(__PLAN9_SOURCE) || defined(__SLIBC_SOURCE)
+/**
+ * Search the environment variable $PATH for an executable
+ * file whose name is the specified name. Slashes are ignored
+ * and treated as any other character. $PATH is searched
+ * for left to right, and ':' is used as the path-delimiter.
+ * 
+ * $PATH is not inspected if `name` starts with './', '../', or '/'.
+ * 
+ * This is a Plan 9 from Bell Labs compliant slibc extension.
+ * 
+ * @etymology  (Search) ($PATH) for an executable.
+ * 
+ * @param   name  The name of the sought executable. Must not be `NULL`.
+ * @return        The pathname of the sought file, `NULL` on error,
+ *                or if not found. Files that are not executable
+ *                are (almost) ignored.
+ * 
+ * @throws  ENOMEM  The process cannot allocate enough memory.
+ * @throws  ENOENT  $PATH is not defined, there are no directories
+ *                  listed in $PATH, or the sought file is not
+ *                  exist inside $PATH.
+ * @throws  EACCES  None of the candidates (with at least one candidate)
+ *                  are executable by the current user.
+ * @throws          Any exception defined for access(3), except for `EROFS`,
+ *                  these when encountered, the search is aborted, unless
+ *                  the error is `ENOENT` or `EACCES`.
+ * 
+ * @since  Always.
+ */
+char* searchpath(const char*);
+#endif
+
+#if defined(__SLIBC_SOURCE)
+/**
+ * Search the environment variable $PATH for an executable
+ * file whose name is the specified name. Slashes are ignored
+ * and treated as any other character. $PATH is searched
+ * for left to right, and ':' is used as the path-delimiter.
+ * If $PATH is not defined, a fallback value for $PATH will be
+ * used.
+ * 
+ * $PATH is not inspected if `name` starts with './', '../', or '/'.
+ * 
+ * This function was added because it was useful in implementing
+ * the `exec`-function family.
+ * 
+ * This is a slibc extension.
+ * 
+ * @etymology  Variant of (searchpath) that takes (2) arguments.
+ * 
+ * @param   name      The name of the sought executable. Must not be `NULL`.
+ * @param   fallback  Value to use instead of the value of $PATH, if
+ *                    path is not defined. If `NULL` the fall rules for
+ *                    the `exec`-functions are used: The current working
+ *                    directory, and the default value for $PATH.
+ * @return            The pathname of the sought file, `NULL` on error,
+ *                    or if not found. Files that are not executable
+ *                    are (almost) ignored.
+ * 
+ * @throws  ENOMEM  The process cannot allocate enough memory.
+ * @throws  ENOENT  There are no directories listed in $PATH, or
+ *                  the sought file is not exist inside $PATH.
+ * @throws  EACCES  None of the candidates (with at least one candidate)
+ *                  are executable by the current user.
+ * @throws          Any exception defined for access(3), except for `EROFS`,
+ *                  these when encountered, the search is aborted, unless
+ *                  the error is `ENOENT` or `EACCES`.
+ * 
+ * @since  Always.
+ */
+char* searchpath2(const char*, const char*);
+#endif
+
 
 
 #endif
diff --git a/src/unistd/exec.c b/src/unistd/exec.c
index c6e4fca..9375baa 100644
--- a/src/unistd/exec.c
+++ b/src/unistd/exec.c
@@ -21,9 +21,6 @@
 #include <alloca.h>
 #include <string.h>
 #include <stdlib.h>
-/* TODO temporary contants from other headers { */
-#define _CS_PATH 1
-/* } */
 
 
 
@@ -323,12 +320,7 @@ int execve(const char* path, char* const argv[], char* const envp[])
  */
 int execvpe(const char* file, char* const argv[], char* const envp[])
 {
-  char* path = NULL;
   char* pathname = NULL;
-  char* p;
-  char* q;
-  size_t len = 0;
-  int eacces = 0;
   int saved_errno;
   
   if (strchr(file, '/'))
@@ -337,49 +329,13 @@ int execvpe(const char* file, char* const argv[], char* const envp[])
   if (!*file)
     return errno = ENOENT, -1;
   
-  path = getenv("PATH");
-  if (path == NULL)
-    {
-      if ((len = confstr(_CS_PATH, NULL, 0)))
-	{
-	  path = malloc((2 + len) * sizeof(char));
-	  if (path == NULL)
-	    goto fail;
-	  if (!confstr(_CS_PATH, stpcpy(path, ".:"), len))
-	    free(path), path = NULL;
-	}
-      if (path == NULL)
-	path = strdup(".:/usr/local/bin:/bin:/usr/bin");
-    }
-  else
-    path = strdup(path);
-  if (path == NULL)
-    goto fail;
-  
-  pathname = malloc((strlen(path) + strlen(file) + 2) * sizeof(char));
+  pathname = searchpath2(file, NULL);
   if (pathname == NULL)
-    goto fail;
-  
-  for (p = path; *p; p = q + 1)
-    {
-      if (p == (q = strchr(p, ':')))
-	continue;
-      *q = '\0';
-      
-      stpcpy(stpcpy(stpcpy(pathname, p), "/"), file);
-      
-      execve(pathname, argv, envp);
-      if      (errno == EACCES)  eacces = 1;
-      else if (errno != ENOENT)  goto fail;
-    }
+    return -1;
   
-  free(path);
-  free(pathname);
-  return errno = (eacces ? EACCES : ENOENT), -1;
+  execve(pathname, argv, envp);
   
- fail:
   saved_errno = errno;
-  free(path);
   free(pathname);
   errno = saved_errno;
   return -1;
diff --git a/src/unistd/searchpath.c b/src/unistd/searchpath.c
new file mode 100644
index 0000000..84bc58e
--- /dev/null
+++ b/src/unistd/searchpath.c
@@ -0,0 +1,55 @@
+/**
+ * slibc — Yet another C library
+ * Copyright © 2015  Mattias Andrée (maandree@member.fsf.org)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <unistd.h>
+
+
+
+/**
+ * Search the environment variable $PATH for an executable
+ * file whose name is the specified name. Slashes are ignored
+ * and treated as any other character. $PATH is searched
+ * for left to right, and ':' is used as the path-delimiter.
+ * 
+ * $PATH is not inspected if `name` starts with './', '../', or '/'.
+ * 
+ * This is a Plan 9 from Bell Labs compliant slibc extension.
+ * 
+ * @etymology  (Search) ($PATH) for an executable.
+ * 
+ * @param   name  The name of the sought executable.
+ * @return        The pathname of the sought file, `NULL` on error,
+ *                or if not found. Files that are not executable
+ *                are (almost) ignored.
+ * 
+ * @throws  ENOMEM  The process cannot allocate enough memory.
+ * @throws  ENOENT  $PATH is not defined, there are no directories
+ *                  listed in $PATH, or the sought file is not
+ *                  exist inside $PATH.
+ * @throws  EACCES  None of the candidates (with at least one candidate)
+ *                  are executable by the current user.
+ * @throws          Any exception defined for access(3), except for `EROFS`,
+ *                  these when encountered, the search is aborted, unless
+ *                  the error is `ENOENT` or `EACCES`.
+ * 
+ * @since  Always.
+ */
+char* searchpath(const char* name)
+{
+  return searchpath2(name, "");
+}
+
diff --git a/src/unistd/searchpath2.c b/src/unistd/searchpath2.c
new file mode 100644
index 0000000..9921550
--- /dev/null
+++ b/src/unistd/searchpath2.c
@@ -0,0 +1,132 @@
+/**
+ * slibc — Yet another C library
+ * Copyright © 2015  Mattias Andrée (maandree@member.fsf.org)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+/* TODO temporary contants from other headers { */
+#define _CS_PATH 1
+/* } */
+
+
+
+/**
+ * Search the environment variable $PATH for an executable
+ * file whose name is the specified name. Slashes are ignored
+ * and treated as any other character. $PATH is searched
+ * for left to right, and ':' is used as the path-delimiter.
+ * If $PATH is not defined, a fallback value for $PATH will be
+ * used.
+ * 
+ * $PATH is not inspected if `name` starts with './', '../', or '/'.
+ * 
+ * This function was added because it was useful in implementing
+ * the `exec`-function family.
+ * 
+ * This is a slibc extension.
+ * 
+ * @etymology  Variant of (searchpath) that takes (2) arguments.
+ * 
+ * @param   name      The name of the sought executable. Must not be `NULL`.
+ * @param   fallback  Value to use instead of the value of $PATH, if
+ *                    path is not defined. If `NULL` the fall rules for
+ *                    the `exec`-functions are used: The current working
+ *                    directory, and the default value for $PATH.
+ * @return            The pathname of the sought file, `NULL` on error,
+ *                    or if not found. Files that are not executable
+ *                    are (almost) ignored.
+ * 
+ * @throws  ENOMEM  The process cannot allocate enough memory.
+ * @throws  ENOENT  There are no directories listed in $PATH, or
+ *                  the sought file is not exist inside $PATH.
+ * @throws  EACCES  None of the candidates (with at least one candidate)
+ *                  are executable by the current user.
+ * @throws          Any exception defined for access(3), except for `EROFS`,
+ *                  these when encountered, the search is aborted, unless
+ *                  the error is `ENOENT` or `EACCES`.
+ * 
+ * @since  Always.
+ */
+char* searchpath2(const char* name, const char* fallback)
+{
+  char* path;
+  size_t len = 0;
+  char* pathname = NULL;
+  char* p;
+  char* q;
+  int eacces = 0;
+  int saved_errno;
+  
+  if (strstarts(name, "./") || strstarts(name, "../") || strstarts(name, "/"))
+    {
+      if (access(name, X_OK) == 0)
+	return strdup(name);
+      return NULL;
+    }
+  
+  path = getenv("PATH");
+  if ((path == NULL) && (fallback == NULL))
+    {
+      if ((len = confstr(_CS_PATH, NULL, 0)))
+	{
+	  path = malloc((2 + len) * sizeof(char));
+	  if (path == NULL)
+	    goto fail;
+	  if (!confstr(_CS_PATH, stpcpy(path, ".:"), len))
+	    free(path), path = NULL;
+	}
+      if (path == NULL)
+	path = strdup(".:/usr/local/bin:/bin:/usr/bin");
+    }
+  else
+    path = strdup(path == NULL ? fallback : path);
+  if (path == NULL)
+    goto fail;
+  
+  pathname = malloc((strlen(path) + strlen(name) + 2) * sizeof(char));
+  if (pathname == NULL)
+    goto fail;
+  
+  for (p = path; *p; p = q + 1)
+    {
+      if (p == (q = strchr(p, ':')))
+	continue;
+      *q = '\0';
+      
+      stpcpy(stpcpy(stpcpy(pathname, p), "/"), name);
+      
+      if (access(pathname, X_OK) == 0)
+	{
+	  char* truncated = realloc(pathname, (strlen(pathname) + 1) * sizeof(char));
+	  return truncated == NULL ? pathname : truncated;
+	}
+      else if (errno == EACCES)  eacces = 1;
+      else if (errno != ENOENT)  goto fail;
+    }
+  
+  free(path);
+  free(pathname);
+  return errno = (eacces ? EACCES : ENOENT), -1;
+  
+ fail:
+  saved_errno = errno;
+  free(path);
+  free(pathname);
+  errno = saved_errno;
+  return -1;
+}
+
-- 
cgit v1.2.3-70-g09d2