summaryrefslogtreecommitdiff
path: root/compat/pathfind.c
diff options
context:
space:
mode:
Diffstat (limited to 'compat/pathfind.c')
-rw-r--r--compat/pathfind.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/compat/pathfind.c b/compat/pathfind.c
new file mode 100644
index 0000000..3f66812
--- /dev/null
+++ b/compat/pathfind.c
@@ -0,0 +1,335 @@
+/* -*- Mode: C -*- */
+
+/* pathfind.c --- find a FILE MODE along PATH */
+
+/*
+ * Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk>
+ * Time-stamp: "2012-08-11 08:19:39 bkorb"
+ */
+
+/* Code: */
+
+#include "compat.h"
+#ifndef HAVE_PATHFIND
+#if defined(__windows__) && !defined(__CYGWIN__)
+char*
+pathfind( char const* path,
+ char const* fileName,
+ char const* mode )
+{
+ return NULL;
+}
+#else
+
+static char* make_absolute( char const *string, char const *dot_path );
+static char* canonicalize_pathname( char *path );
+static char* extract_colon_unit( char* dir, char const *string, int *p_index );
+
+
+/*=export_func pathfind
+ *
+ * what: fild a file in a list of directories
+ *
+ * ifndef: HAVE_PATHFIND
+ *
+ * arg: + char const* + path + colon separated list of search directories +
+ * arg: + char const* + file + the name of the file to look for +
+ * arg: + char const* + mode + the mode bits that must be set to match +
+ *
+ * ret_type: char*
+ * ret_desc: the path to the located file
+ *
+ * doc:
+ *
+ * pathfind looks for a a file with name "FILE" and "MODE" access
+ * along colon delimited "PATH", and returns the full pathname as a
+ * string, or NULL if not found. If "FILE" contains a slash, then
+ * it is treated as a relative or absolute path and "PATH" is ignored.
+ *
+ * @strong{NOTE}: this function is compiled into @file{libopts} only if
+ * it is not natively supplied.
+ *
+ * The "MODE" argument is a string of option letters chosen from the
+ * list below:
+ * @example
+ * Letter Meaning
+ * r readable
+ * w writable
+ * x executable
+ * f normal file (NOT IMPLEMENTED)
+ * b block special (NOT IMPLEMENTED)
+ * c character special (NOT IMPLEMENTED)
+ * d directory (NOT IMPLEMENTED)
+ * p FIFO (pipe) (NOT IMPLEMENTED)
+ * u set user ID bit (NOT IMPLEMENTED)
+ * g set group ID bit (NOT IMPLEMENTED)
+ * k sticky bit (NOT IMPLEMENTED)
+ * s size nonzero (NOT IMPLEMENTED)
+ * @end example
+ *
+ * example:
+ * To find the "ls" command using the "PATH" environment variable:
+ * @example
+ * #include <stdlib.h>
+ * char* pz_ls = pathfind( getenv("PATH"), "ls", "rx" );
+ * <<do whatever with pz_ls>>
+ * free( pz_ls );
+ * @end example
+ * The path is allocated with @code{malloc(3C)}, so you must @code{free(3C)}
+ * the result. Also, do not use unimplemented file modes. :-)
+ *
+ * err: returns NULL if the file is not found.
+=*/
+char*
+pathfind( char const* path,
+ char const* fileName,
+ char const* mode )
+{
+ int p_index = 0;
+ int mode_bits = 0;
+ char* pathName = NULL;
+ char zPath[ AG_PATH_MAX + 1 ];
+
+ if (strchr( mode, 'r' )) mode_bits |= R_OK;
+ if (strchr( mode, 'w' )) mode_bits |= W_OK;
+ if (strchr( mode, 'x' )) mode_bits |= X_OK;
+
+ /*
+ * FOR each non-null entry in the colon-separated path, DO ...
+ */
+ for (;;) {
+ DIR* dirP;
+ char* colon_unit = extract_colon_unit( zPath, path, &p_index );
+
+ /*
+ * IF no more entries, THEN quit
+ */
+ if (colon_unit == NULL)
+ break;
+
+ dirP = opendir( colon_unit );
+
+ /*
+ * IF the directory is inaccessable, THEN next directory
+ */
+ if (dirP == NULL)
+ continue;
+
+ /*
+ * FOR every entry in the given directory, ...
+ */
+ for (;;) {
+ struct dirent *entP = readdir( dirP );
+
+ if (entP == (struct dirent*)NULL)
+ break;
+
+ /*
+ * IF the file name matches the one we are looking for, ...
+ */
+ if (strcmp( entP->d_name, fileName ) == 0) {
+ char* pzFullName = make_absolute( fileName, colon_unit);
+
+ /*
+ * Make sure we can access it in the way we want
+ */
+ if (access( pzFullName, mode_bits ) >= 0) {
+ /*
+ * We can, so normalize the name and return it below
+ */
+ pathName = canonicalize_pathname( pzFullName );
+ }
+
+ free( (void*)pzFullName );
+ break;
+ }
+ }
+
+ closedir( dirP );
+
+ if (pathName != NULL)
+ break;
+ }
+
+ return pathName;
+}
+
+/*
+ * Turn STRING (a pathname) into an absolute pathname, assuming that
+ * DOT_PATH contains the symbolic location of `.'. This always returns
+ * a new string, even if STRING was an absolute pathname to begin with.
+ */
+static char*
+make_absolute( char const *string, char const *dot_path )
+{
+ char *result;
+ int result_len;
+
+ if (!dot_path || *string == '/') {
+ result = strdup( string );
+ } else {
+ if (dot_path && dot_path[0]) {
+ result = malloc( 2 + strlen( dot_path ) + strlen( string ) );
+ strcpy( result, dot_path );
+ result_len = strlen( result );
+ if (result[result_len - 1] != '/') {
+ result[result_len++] = '/';
+ result[result_len] = '\0';
+ }
+ } else {
+ result = malloc( 3 + strlen( string ) );
+ result[0] = '.'; result[1] = '/'; result[2] = '\0';
+ result_len = 2;
+ }
+
+ strcpy( result + result_len, string );
+ }
+
+ return result;
+}
+
+/*
+ * Canonicalize PATH, and return a new path. The new path differs from
+ * PATH in that:
+ *
+ * Multiple `/'s are collapsed to a single `/'.
+ * Leading `./'s are removed.
+ * Trailing `/.'s are removed.
+ * Trailing `/'s are removed.
+ * Non-leading `../'s and trailing `..'s are handled by removing
+ * portions of the path.
+ */
+static char*
+canonicalize_pathname( char *path )
+{
+ int i, start;
+ char stub_char, *result;
+
+ /* The result cannot be larger than the input PATH. */
+ result = strdup( path );
+
+ stub_char = (*path == '/') ? '/' : '.';
+
+ /* Walk along RESULT looking for things to compact. */
+ i = 0;
+ while (result[i]) {
+ while (result[i] != '\0' && result[i] != '/')
+ i++;
+
+ start = i++;
+
+ /* If we didn't find any slashes, then there is nothing left to
+ * do.
+ */
+ if (!result[start])
+ break;
+
+ /* Handle multiple `/'s in a row. */
+ while (result[i] == '/')
+ i++;
+
+#if !defined (apollo)
+ if ((start + 1) != i)
+#else
+ if ((start + 1) != i && (start != 0 || i != 2))
+#endif /* apollo */
+ {
+ strcpy( result + start + 1, result + i );
+ i = start + 1;
+ }
+
+ /* Handle backquoted `/'. */
+ if (start > 0 && result[start - 1] == '\\')
+ continue;
+
+ /* Check for trailing `/', and `.' by itself. */
+ if ((start && !result[i])
+ || (result[i] == '.' && !result[i+1])) {
+ result[--i] = '\0';
+ break;
+ }
+
+ /* Check for `../', `./' or trailing `.' by itself. */
+ if (result[i] == '.') {
+ /* Handle `./'. */
+ if (result[i + 1] == '/') {
+ strcpy( result + i, result + i + 1 );
+ i = (start < 0) ? 0 : start;
+ continue;
+ }
+
+ /* Handle `../' or trailing `..' by itself. */
+ if (result[i + 1] == '.' &&
+ (result[i + 2] == '/' || !result[i + 2])) {
+ while (--start > -1 && result[start] != '/')
+ ;
+ strcpy( result + start + 1, result + i + 2 );
+ i = (start < 0) ? 0 : start;
+ continue;
+ }
+ }
+ }
+
+ if (!*result) {
+ *result = stub_char;
+ result[1] = '\0';
+ }
+
+ return result;
+}
+
+/*
+ * Given a string containing units of information separated by colons,
+ * return the next one pointed to by (P_INDEX), or NULL if there are no
+ * more. Advance (P_INDEX) to the character after the colon.
+ */
+static char*
+extract_colon_unit( char* pzDir, char const *string, int *p_index )
+{
+ char* pzDest = pzDir;
+ int ix = *p_index;
+
+ if (string == NULL)
+ return NULL;
+
+ if ((unsigned)ix >= strlen( string ))
+ return NULL;
+
+ {
+ char const* pzSrc = string + ix;
+
+ while (*pzSrc == ':') pzSrc++;
+
+ for (;;) {
+ char ch = (*(pzDest++) = *(pzSrc++));
+ switch (ch) {
+ case ':':
+ pzDest[-1] = NUL;
+ /* FALLTHROUGH */
+ case NUL:
+ goto copy_done;
+ }
+
+ if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX)
+ break;
+ } copy_done:;
+
+ ix = pzSrc - string;
+ }
+
+ if (*pzDir == NUL)
+ return NULL;
+
+ *p_index = ix;
+ return pzDir;
+}
+#endif /* __windows__ / __CYGWIN__ */
+#endif /* HAVE_PATHFIND */
+
+/*
+ * Local Variables:
+ * mode: C
+ * c-file-style: "stroustrup"
+ * indent-tabs-mode: nil
+ * End:
+ * end of compat/pathfind.c */