/**********************************************************************

  dln_find.c -

  $Author$
  created at: Tue Jan 18 17:05:06 JST 1994

  Copyright (C) 1993-2007 Yukihiro Matsumoto

**********************************************************************/

#ifdef RUBY_EXPORT
#include "ruby/ruby.h"
#define dln_warning rb_warning
#define dln_warning_arg
#else
#define dln_warning fprintf
#define dln_warning_arg stderr,
#endif
#include "dln.h"

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#if defined(HAVE_ALLOCA_H)
#include <alloca.h>
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#else
# include <strings.h>
#endif

#include <stdio.h>
#if defined(_WIN32)
#include "missing/file.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>

#ifndef S_ISDIR
#   define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#if !defined(_WIN32) && !HAVE_DECL_GETENV
char *getenv();
#endif

static char *dln_find_1(const char *fname, const char *path, char *buf, size_t size, int exe_flag
                        DLN_FIND_EXTRA_ARG_DECL);

char *
dln_find_exe_r(const char *fname, const char *path, char *buf, size_t size
               DLN_FIND_EXTRA_ARG_DECL)
{
    char *envpath = 0;

    if (!path) {
        path = getenv(PATH_ENV);
        if (path) path = envpath = strdup(path);
    }

    if (!path) {
        path =
            "/usr/local/bin" PATH_SEP
            "/usr/ucb" PATH_SEP
            "/usr/bin" PATH_SEP
            "/bin" PATH_SEP
            ".";
    }
    buf = dln_find_1(fname, path, buf, size, 1 DLN_FIND_EXTRA_ARG);
    free(envpath);
    return buf;
}

char *
dln_find_file_r(const char *fname, const char *path, char *buf, size_t size
                DLN_FIND_EXTRA_ARG_DECL)
{
    if (!path) path = ".";
    return dln_find_1(fname, path, buf, size, 0 DLN_FIND_EXTRA_ARG);
}

static char *
dln_find_1(const char *fname, const char *path, char *fbuf, size_t size,
           int exe_flag /* non 0 if looking for executable. */
           DLN_FIND_EXTRA_ARG_DECL)
{
    register const char *dp;
    register const char *ep;
    register char *bp;
    struct stat st;
    size_t i, fnlen, fspace;
#ifdef DOSISH
    static const char extension[][5] = {
        EXECUTABLE_EXTS,
    };
    size_t j;
    int is_abs = 0, has_path = 0;
    const char *ext = 0;
#endif
    const char *p = fname;

    static const char pathname_too_long[] = "openpath: pathname too long (ignored)\n\
\tDirectory \"%.*s\"%s\n\tFile \"%.*s\"%s\n";
#define PATHNAME_TOO_LONG() dln_warning(dln_warning_arg pathname_too_long, \
                                        ((bp - fbuf) > 100 ? 100 : (int)(bp - fbuf)), fbuf, \
                                        ((bp - fbuf) > 100 ? "..." : ""), \
                                        (fnlen > 100 ? 100 : (int)fnlen), fname, \
                                        (fnlen > 100 ? "..." : ""))

#define RETURN_IF(expr) if (expr) return (char *)fname;

    RETURN_IF(!fname);
    fnlen = strlen(fname);
    if (fnlen >= size) {
        dln_warning(dln_warning_arg
                    "openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n",
                    (fnlen > 100 ? 100 : (int)fnlen), fname,
                    (fnlen > 100 ? "..." : ""));
        return NULL;
    }
#ifdef DOSISH
# ifndef CharNext
# define CharNext(p) ((p)+1)
# endif
# ifdef DOSISH_DRIVE_LETTER
    if (((p[0] | 0x20) - 'a') < 26  && p[1] == ':') {
        p += 2;
        is_abs = 1;
    }
# endif
    switch (*p) {
      case '/': case '\\':
        is_abs = 1;
        p++;
    }
    has_path = is_abs;
    while (*p) {
        switch (*p) {
          case '/': case '\\':
            has_path = 1;
            ext = 0;
            p++;
            break;
          case '.':
            ext = p;
            p++;
            break;
          default:
            p = CharNext(p);
        }
    }
    if (ext) {
        for (j = 0; STRCASECMP(ext, extension[j]); ) {
            if (++j == sizeof(extension) / sizeof(extension[0])) {
                ext = 0;
                break;
            }
        }
    }
    ep = bp = 0;
    if (!exe_flag) {
        RETURN_IF(is_abs);
    }
    else if (has_path) {
        RETURN_IF(ext);
        i = p - fname;
        if (i + 1 > size) goto toolong;
        fspace = size - i - 1;
        bp = fbuf;
        ep = p;
        memcpy(fbuf, fname, i + 1);
        goto needs_extension;
    }
    p = fname;
#endif

    if (*p == '.' && *++p == '.') ++p;
    RETURN_IF(*p == '/');
    RETURN_IF(exe_flag && strchr(fname, '/'));

#undef RETURN_IF

    for (dp = path;; dp = ++ep) {
        register size_t l;

        /* extract a component */
        ep = strchr(dp, PATH_SEP[0]);
        if (ep == NULL)
            ep = dp+strlen(dp);

        /* find the length of that component */
        l = ep - dp;
        bp = fbuf;
        fspace = size - 2;
        if (l > 0) {
            /*
            **	If the length of the component is zero length,
            **	start from the current directory.  If the
            **	component begins with "~", start from the
            **	user's $HOME environment variable.  Otherwise
            **	take the path literally.
            */

            if (*dp == '~' && (l == 1 ||
#if defined(DOSISH)
                               dp[1] == '\\' ||
#endif
                               dp[1] == '/')) {
                const char *home;

                home = getenv("HOME");
                if (home != NULL) {
                    i = strlen(home);
                    if (fspace < i)
                        goto toolong;
                    fspace -= i;
                    memcpy(bp, home, i);
                    bp += i;
                }
                dp++;
                l--;
            }
            if (l > 0) {
                if (fspace < l)
                    goto toolong;
                fspace -= l;
                memcpy(bp, dp, l);
                bp += l;
            }

            /* add a "/" between directory and filename */
            if (ep[-1] != '/')
                *bp++ = '/';
        }

        /* now append the file name */
        i = fnlen;
        if (fspace < i) {
            goto toolong;
        }
        fspace -= i;
        memcpy(bp, fname, i + 1);

#if defined(DOSISH)
        if (exe_flag && !ext) {
            goto needs_extension;
        }
#endif

#ifndef S_ISREG
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
        if (stat(fbuf, &st) == 0 && S_ISREG(st.st_mode)) {
            if (exe_flag == 0) return fbuf;
            /* looking for executable */
            if (eaccess(fbuf, X_OK) == 0) return fbuf;
        }
      next:
        /* if not, and no other alternatives, life is bleak */
        if (*ep == '\0') {
            return NULL;
        }
        continue;

      toolong:
        PATHNAME_TOO_LONG();
        goto next;

#if defined(DOSISH)
      needs_extension:
        for (j = 0; j < sizeof(extension) / sizeof(extension[0]); j++) {
            if (fspace < strlen(extension[j])) {
                PATHNAME_TOO_LONG();
                continue;
            }
            strlcpy(bp + i, extension[j], fspace);
            if (stat(fbuf, &st) == 0)
                return fbuf;
        }
        goto next;
#endif
        /* otherwise try the next component in the search path */
    }
}
