bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
From: Bruno Haible <bruno@clisp.org>
To: psmith@gnu.org
Cc: bug-gnulib@gnu.org
Subject: Re: [PATCH] findprog: Support searching in a specified path string
Date: Sun, 08 Sep 2019 18:25:40 +0200	[thread overview]
Message-ID: <3294972.9is0SCQsEX@omega> (raw)
In-Reply-To: <2257421.7xAeYRz9PB@omega>

Hi Paul,

Here's the implementation I'm committing.

I feel that merging the two functions in a single file would add more
contortions than benefit.


2019-09-08  Bruno Haible  <bruno@clisp.org>

	findprog-in: New module.
	Suggested by Paul Smith <psmith@gnu.org>.
	* lib/findprog.h (find_in_given_path): New declaration.
	* lib/findprog-in.c: New file, based on lib/findprog.c.
	* m4/findprog-in.m4: New file, based on m4/findprog.m4.
	* modules/findprog-in: New file.

diff --git a/lib/findprog.h b/lib/findprog.h
index a354f67..9bc8a60 100644
--- a/lib/findprog.h
+++ b/lib/findprog.h
@@ -21,16 +21,29 @@ extern "C" {
 #endif
 
 
-/* Look up a program in the PATH.
-   Attempt to determine the pathname that would be called by execlp/execvp
-   of PROGNAME.  If successful, return a pathname containing a slash
-   (either absolute or relative to the current directory).  Otherwise,
-   return PROGNAME unmodified.
+/* Looks up a program in the PATH.
+   Attempts to determine the pathname that would be called by execlp/execvp
+   of PROGNAME.  If successful, it returns a pathname containing a slash
+   (either absolute or relative to the current directory).  Otherwise, it
+   returns PROGNAME unmodified.
    Because of the latter case, callers should use execlp/execvp, not
    execl/execv on the returned pathname.
    The returned string is freshly malloc()ed if it is != PROGNAME.  */
 extern const char *find_in_path (const char *progname);
 
+/* Looks up a program in the given PATH-like string.
+   The PATH argument consists of a list of directories, separated by ':' or
+   (on native Windows) by ';'.  An empty PATH element designates the current
+   directory.  A null PATH is equivalent to an empty PATH, that is, to the
+   singleton list that contains only the current directory.
+   Determines the pathname that would be called by execlp/execvp of PROGNAME.
+   - If successful, it returns a pathname containing a slash (either absolute
+     or relative to the current directory).  The returned string can be used
+     with either execl/execv or execlp/execvp.  It is freshly malloc()ed if it
+     is != PROGNAME.
+   - Otherwise, it returns NULL.  */
+extern const char *find_in_given_path (const char *progname, const char *path);
+
 
 #ifdef __cplusplus
 }
diff --git a/lib/findprog-in.c b/lib/findprog-in.c
new file mode 100644
index 0000000..3d70b7b
--- /dev/null
+++ b/lib/findprog-in.c
@@ -0,0 +1,178 @@
+/* Locating a program in a given path.
+   Copyright (C) 2001-2004, 2006-2019 Free Software Foundation, Inc.
+   Written by Bruno Haible <haible@clisp.cons.org>, 2001, 2019.
+
+   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 <https://www.gnu.org/licenses/>.  */
+
+
+#include <config.h>
+
+/* Specification.  */
+#include "findprog.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "filename.h"
+#include "concat-filename.h"
+#include "xalloc.h"
+
+#if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
+  /* Native Windows, OS/2, DOS */
+# define NATIVE_SLASH '\\'
+#else
+  /* Unix */
+# define NATIVE_SLASH '/'
+#endif
+
+/* Separator in PATH like lists of pathnames.  */
+#if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
+  /* Native Windows, OS/2, DOS */
+# define PATH_SEPARATOR ';'
+#else
+  /* Unix */
+# define PATH_SEPARATOR ':'
+#endif
+
+/* The list of suffixes that the execlp/execvp function tries when searching
+   for the program.  */
+static const char * const suffixes[] =
+  {
+    #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
+    "", ".com", ".exe", ".bat", ".cmd"
+    /* Note: Files without any suffix are not considered executable.  */
+    /* Note: The cmd.exe program does a different lookup: It searches according
+       to the PATHEXT environment variable.
+       See <https://stackoverflow.com/questions/7839150/>.
+       Also, it executes files ending .bat and .cmd directly without letting the
+       kernel interpret the program file.  */
+    #elif defined __CYGWIN__
+    "", ".exe", ".com"
+    #elif defined __EMX__
+    "", ".exe"
+    #elif defined __DJGPP__
+    "", ".com", ".exe", ".bat"
+    #else /* Unix */
+    ""
+    #endif
+  };
+
+const char *
+find_in_given_path (const char *progname, const char *path)
+{
+  {
+    bool has_slash = false;
+    const char *p;
+
+    for (p = progname; *p != '\0'; p++)
+      if (ISSLASH (*p))
+        {
+          has_slash = true;
+          break;
+        }
+    if (has_slash)
+      /* If progname contains a slash, it is either absolute or relative to
+         the current directory.  PATH is not used.
+         We could try the various suffixes and see whether one of the files
+         with such a suffix is actually executable.  But this is not needed,
+         since the execl/execv/execlp/execvp functions will do these tests
+         anyway.  */
+      return progname;
+  }
+
+  if (path == NULL)
+    /* If PATH is not set, the default search path is implementation dependent.
+       In practice, it is treated like an empty PATH.  */
+    path = "";
+
+  {
+    /* Make a copy, to prepare for destructive modifications.  */
+    char *path_copy = xstrdup (path);
+    char *path_rest;
+    char *cp;
+
+    for (path_rest = path_copy; ; path_rest = cp + 1)
+      {
+        const char *dir;
+        bool last;
+        size_t i;
+
+        /* Extract next directory in PATH.  */
+        dir = path_rest;
+        for (cp = path_rest; *cp != '\0' && *cp != PATH_SEPARATOR; cp++)
+          ;
+        last = (*cp == '\0');
+        *cp = '\0';
+
+        /* Empty PATH components designate the current directory.  */
+        if (dir == cp)
+          dir = ".";
+
+        /* Try all platform-dependent suffixes.  */
+        for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
+          {
+            const char *suffix = suffixes[i];
+
+            #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
+            /* File names without a '.' are not considered executable.  */
+            if (*suffix != '\0' || strchr (progname, '.') != NULL)
+            #endif
+              {
+                /* Concatenate dir and progname.  */
+                char *progpathname =
+                  xconcatenated_filename (dir, progname, suffix);
+
+                /* On systems which have the eaccess() system call, let's use
+                   it.  On other systems, let's hope that this program is not
+                   installed setuid or setgid, so that it is ok to call
+                   access() despite its design flaw.  */
+                if (eaccess (progpathname, X_OK) == 0)
+                  {
+                    /* Found!  */
+                    if (strcmp (progpathname, progname) == 0)
+                      {
+                        free (progpathname);
+
+                        /* Add the "./" prefix for real, that
+                           xconcatenated_filename() optimized away.  This
+                           avoids a second PATH search when the caller uses
+                           execl/execv/execlp/execvp.  */
+                        progpathname =
+                          XNMALLOC (2 + strlen (progname) + 1, char);
+                        progpathname[0] = '.';
+                        progpathname[1] = NATIVE_SLASH;
+                        memcpy (progpathname + 2, progname,
+                                strlen (progname) + 1);
+                      }
+
+                    free (path_copy);
+                    return progpathname;
+                  }
+
+                free (progpathname);
+              }
+          }
+
+        if (last)
+          break;
+      }
+
+    /* Not found in PATH.  */
+    free (path_copy);
+  }
+
+  return NULL;
+}
diff --git a/m4/findprog-in.m4 b/m4/findprog-in.m4
new file mode 100644
index 0000000..baa7e6b
--- /dev/null
+++ b/m4/findprog-in.m4
@@ -0,0 +1,11 @@
+# findprog-in.m4 serial 1
+dnl Copyright (C) 2003, 2009-2019 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FINDPROG_IN],
+[
+  dnl Prerequisites of lib/findprog-in.c.
+  AC_REQUIRE([gl_FUNC_EACCESS])
+])
diff --git a/modules/findprog-in b/modules/findprog-in
new file mode 100644
index 0000000..ce7faa5
--- /dev/null
+++ b/modules/findprog-in
@@ -0,0 +1,30 @@
+Description:
+Locating a program in a given path.
+
+Files:
+lib/findprog.h
+lib/findprog-in.c
+m4/findprog-in.m4
+m4/eaccess.m4
+
+Depends-on:
+stdbool
+filename
+xalloc
+xconcat-filename
+unistd
+
+configure.ac:
+gl_FINDPROG_IN
+
+Makefile.am:
+lib_SOURCES += findprog.h findprog-in.c
+
+Include:
+"findprog.h"
+
+License:
+GPL
+
+Maintainer:
+all



  reply	other threads:[~2019-09-08 16:26 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-09-06 23:10 [PATCH] findprog: Support searching in a specified path string Paul Smith
2019-09-06 23:21 ` Paul Smith
2019-09-07 10:42 ` Bruno Haible
2019-09-07 13:17   ` Paul Smith
2019-09-08 11:38     ` Bruno Haible
2019-09-08 14:03       ` Paul Smith
2019-09-08 14:59         ` Bruno Haible
2019-09-08 16:25           ` Bruno Haible [this message]
2019-09-08 17:34           ` Paul Smith
2019-09-08 17:48             ` Bruno Haible
2019-09-08 17:59               ` Paul Smith
2019-09-09 18:54                 ` Bruno Haible
2019-09-10 13:18                   ` Paul Smith
2019-09-14 11:20                     ` Bruno Haible

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://lists.gnu.org/mailman/listinfo/bug-gnulib

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3294972.9is0SCQsEX@omega \
    --to=bruno@clisp.org \
    --cc=bug-gnulib@gnu.org \
    --cc=psmith@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).