Logo Search packages:      
Sourcecode: vile version File versions

path.c

/*    PATH.C
 *          The routines in this file handle the conversion of pathname
 *          strings.
 *
 * $Header: /usr/build/vile/vile/RCS/path.c,v 1.135 2003/05/25 20:21:27 tom Exp $
 *
 *
 */

#include "estruct.h"
#include "edef.h"

#if SYS_UNIX
#include <sys/types.h>
#include <pwd.h>
#endif

#if SYS_VMS
#include <starlet.h>
#include <file.h>
#endif

#if SYS_OS2
# define INCL_DOSFILEMGR
# define INCL_ERRORS
# include <os2.h>
#endif

#include "dirstuff.h"

/* C Set did not define S_IFMT */
#if !defined(S_IFMT)
#define S_IFMT (S_IFDIR|S_IFREG)
#endif

#if !defined(S_ISDIR)
# define S_ISDIR(m)  (((m) & S_IFMT) == S_IFDIR)
#endif

#if !defined(S_ISREG)
# define S_ISREG(m)  (((m) & S_IFMT) == S_IFREG)
#endif

#ifndef S_IFIFO
#define S_IFIFO S_IFREG
#endif

#if !defined(S_ISFIFO)
# define S_ISFIFO(m)  (((m) & S_IFMT) == S_IFIFO)
#endif

#if SYS_WINNT
# include <direct.h>
# define curdrive() (_getdrive() + ('A' - 1))
# define curr_dir_on_drive(d) _getdcwd(toUpper(d) - ('A' - 1), temp, sizeof(temp))
#endif

#if SYS_OS2_EMX
# define curdrive() _getdrive()
static char *
curr_dir_on_drive(int d)
{
    static char buffer[NFILEN];
    char *s;
    if (_getcwd1(buffer, d) < 0)
      return 0;
    /* EMX 0.9b documents _getcwd1 fixes slashes, but it doesn't work */
    for (s = buffer; *s; s++)
      if (*s == '\\')
          *s = '/';
    return buffer;
}
#endif

#ifdef GMDRESOLVE_LINKS
#if defined(HAVE_SYS_ITIMER_H) && defined(HAVE_SETITIMER)
#include <sys/itimer.h>
#endif
#endif

static int is_absolute_pathname(char *path);

/*
 * Fake directory-routines for system where we cannot otherwise figure out how
 * to read the directory-file.
 */
#if USE_LS_FOR_DIRS
DIR *
opendir(char *path)
{
    static const char fmt[] = "/bin/ls %s";
    char lscmd[NFILEN + sizeof(fmt)];

    (void) lsprintf(lscmd, fmt, path);
    return npopen(lscmd, "r");
}

DIRENT *
readdir(DIR * dp)
{
    static DIRENT dummy;

    if ((fgets(dummy.d_name, NFILEN, dp)) != NULL) {
      /* zap the newline */
      dummy.d_name[strlen(dummy.d_name) - 1] = EOS;
      return &dummy;
    }
    return 0;
}

int
closedir(DIR * dp)
{
    (void) npclose(dp);
    return 0;
}
#endif

/*
 * Use this routine to fake compatibility with unix directory routines.
 */
#if OLD_STYLE_DIRS
DIRENT *
readdir(DIR * dp)
{
    static DIRENT dbfr;

    return (fread(&dbfr, sizeof(dbfr), 1, dp)
          ? &dbfr
          : (DIRENT *) 0);
}
#endif

#if OPT_MSDOS_PATH
/*
 * If the pathname begins with an MSDOS-drive, return the pointer past it.
 * Otherwise, return null.
 */
char *
is_msdos_drive(char *path)
{
#if OPT_UNC_PATH
    if (is_slashc(path[0]) && is_slashc(path[1]))
      return (path + 1);
#endif
    if (isAlpha(path[0]) && path[1] == ':')
      return (path + 2);
    return 0;
}
#endif

#if OPT_VMS_PATH
#define VMSPATH_END_NODE   1
#define VMSPATH_END_DEV    2
#define VMSPATH_BEGIN_DIR  3
#define VMSPATH_NEXT_DIR   4
#define VMSPATH_END_DIR    5
#define     VMSPATH_BEGIN_FILE 6
#define VMSPATH_BEGIN_TYP  7
#define VMSPATH_BEGIN_VER  8

/*
 * Returns true if the string is delimited in a manner compatible with VMS
 * pathnames.  To be consistent with the use of 'is_pathname()', insist that
 * at least the "[]" characters be given.
 *
 * Complete syntax:
 *    node::device:[dir1.dir2]filename.type;version
 *        ^1     ^2^3   ^4  ^5^6      ^7   ^8
 *
 * 'option' is true:directory, false:file, -true:don't care
 */
int
is_vms_pathname(const char *path, int option)
{
    const char *base = path;
    int this = 0, next = -1;

    if (*path == EOS)         /* this can happen with null buffer-name */
      return FALSE;

    while (ispath(*path)) {
      switch (*path) {
      case '[':
          if (this >= VMSPATH_BEGIN_FILE)
            return FALSE;
          next = VMSPATH_BEGIN_DIR;
          break;
      case ']':
          if (this < VMSPATH_BEGIN_DIR)
            return FALSE;
          if (path != base    /* rooted logical? */
            && path[1] == '['
            && path[-1] == '.')
            path++;
          else
            next = VMSPATH_END_DIR;
          break;
      case '.':
          if (this >= VMSPATH_BEGIN_TYP) {
            if (this >= VMSPATH_BEGIN_VER)
                return FALSE;
            next = VMSPATH_BEGIN_VER;
            break;
          }
          next = (this >= VMSPATH_END_DIR)
            ? VMSPATH_BEGIN_TYP
            : (this >= VMSPATH_BEGIN_DIR
               ? VMSPATH_NEXT_DIR
               : VMSPATH_BEGIN_TYP);
          break;
      case ';':
          next = VMSPATH_BEGIN_VER;
          break;
      case ':':
          if (path[1] == ':') {
            path++;           /* eat "::" */
            if (this >= VMSPATH_END_NODE)
                return FALSE;
            next = VMSPATH_END_NODE;
          } else
            next = VMSPATH_END_DEV;
          break;
      case '!':
      case '/':
      case '~':
          return FALSE; /* a DEC-shell name */
      default:
          if (!ispath(*path))
            return FALSE;
          next = (this == VMSPATH_END_DIR)
            ? VMSPATH_BEGIN_FILE
            : this;
          break;
      }
      if (next < this)
          break;
      this = next;
      path++;
    }

    if ((*path != EOS)
      || (this < next))
      return FALSE;

    if (this == 0)
      this = VMSPATH_BEGIN_FILE;

    return (option == TRUE && (this == VMSPATH_END_DIR))    /* dir? */
      || (option == TRUE && (this == VMSPATH_END_DEV))      /* dev? */
      || (option == FALSE && (this >= VMSPATH_BEGIN_FILE))  /* file? */
      || (option == -TRUE && (this >= VMSPATH_END_DIR       /* anything? */
                        || this < VMSPATH_BEGIN_DIR));
}
#endif

#if OPT_VMS_PATH
/*
 * Returns a pointer to the argument's last path-leaf (i.e., filename).
 */
char *
vms_pathleaf(char *path)
{
    register char *s;
    for (s = skip_string(path);
       s > path && !strchr(":]", s[-1]);
       s--) ;
    return s;
}
#endif

/*
 * Returns a pointer to the argument's last path-leaf (i.e., filename).
 */

#if !OPT_VMS_PATH
#define     unix_pathleaf     pathleaf
#endif

char *
unix_pathleaf(char *path)
{
    register char *s = last_slash(path);
    if (s == 0) {
#if OPT_MSDOS_PATH
      if ((s = is_msdos_drive(path)) == 0)
#endif
          s = path;
    } else
      s++;
    return s;
}

#if OPT_VMS_PATH
char *
pathleaf(char *path)
{
    if (is_vms_pathname(path, -TRUE))
      return vms_pathleaf(path);
    return unix_pathleaf(path);
}
#endif

/*
 * Concatenates a directory and leaf name to form a full pathname.  The result
 * must be no longer than NFILEN.
 */
char *
pathcat(char *dst, const char *path, char *leaf)
{
    char save_path[NFILEN];
    char save_leaf[NFILEN];
    char *s;
    size_t have;

    leaf = vl_strncpy(save_leaf, leaf, NFILEN);       /* leaf may be in dst */

    if (path == 0
      || *path == EOS) {
      (void) strcpy(dst, leaf);
    } else {

      path = vl_strncpy(save_path, path, NFILEN);     /* path may be in dst */

#if OPT_MSDOS_PATH
      if (is_msdos_drive(save_path) != 0
          && is_msdos_drive(leaf) == 0) {
          have = strlen(strcpy(dst, path));
          if (have + strlen(leaf) + 2 < NFILEN) {
            s = dst + have - 1;
            if (!is_slashc(*s) && !is_slashc(*leaf))
                *(++s) = SLASHC;
            (void) strcpy(s + 1, leaf);
          }
      } else
#endif
      if (is_absolute_pathname(leaf)) {
          (void) strcpy(dst, leaf);
      } else {
          have = strlen(strcpy(dst, path));
          if (have + strlen(leaf) + 2 < NFILEN) {
            s = dst + have - 1;

#if OPT_VMS_PATH
            if (!is_vms_pathname(dst, TRUE))    /* could be DecShell */
#endif
                if (!is_slashc(*s)) {
                  *(++s) = SLASHC;
                }

            (void) strcpy(s + 1, leaf);
          }
#if OPT_VMS_PATH
          if (is_vms_pathname(path, -TRUE)
            && is_vms_pathname(leaf, -TRUE)
            && !is_vms_pathname(dst, -TRUE))
            (void) strcpy(dst, leaf);
#endif
      }
    }
    return dst;
}

/*
 * Tests to see if the string contains a slash-delimiter.  If so, return the
 * last one (so we can locate the path-leaf).
 */
char *
last_slash(char *fn)
{
    register char *s;

    if (*fn != EOS)
      for (s = skip_string(fn); s > fn; s--)
          if (is_slashc(s[-1]))
            return s - 1;
    return 0;
}

/*
 * If a pathname begins with "~", lookup the name .
 * On Unix we lookup in the password file, under other systems use the $HOME
 * environment variable only, if it exists.
 * Cache the names that we lookup.
 * Searching the password-file on unix can be slow,
 * and users really don't move that often.
 */

#if SYS_UNIX || !SMALLER
typedef struct _upath {
    struct _upath *next;
    char *name;
    char *path;
} UPATH;

static UPATH *user_paths;

static char *
save_user(const char *name, const char *path)
{
    register UPATH *q;

    if (name != NULL
      && path != NULL
      && (q = typealloc(UPATH)) != NULL) {
      TRACE(("save_user(name=%s, path=%s)\n", name, path));
      if ((q->name = strmalloc(name)) != NULL
          && (q->path = strmalloc(path)) != NULL) {
          q->next = user_paths;
          user_paths = q;
          return q->path;
      } else {
          FreeIfNeeded(q->name);
          FreeIfNeeded(q->path);
          free((char *) q);
      }
    }
    return NULL;
}

static char *
find_user(const char *name)
{
#if SYS_UNIX
    register struct passwd *p;
#endif
    register UPATH *q;

    if (name != NULL) {
      for (q = user_paths; q != NULL; q = q->next) {
          if (!strcmp(q->name, name)) {
            return q->path;
          }
      }
#if SYS_UNIX
      /* not-found, do a lookup.
       * First try getpwnam with the specified name,
       * which will use ~ or whatever was passed
       */
      if (*name != EOS) {
          p = getpwnam(name);
      } else {
          /* *name == EOS
           * so lookup the current uid, and then lookup
           * the name based on that.
           */
          p = getpwuid((int) getuid());
      }

      /* if either of the above lookups worked
       * then save the result
       */
      if (p != 0) {
          return save_user(name, p->pw_dir);
      }
#endif
      if (*name == EOS) {
          char *env = home_dir();
          if (env != 0) {
            return save_user(name, env);
          }
      }
    }
#if SYS_UNIX && VILE_NEEDED
    else {              /* lookup all users (for globbing) */
      (void) setpwent();
      while ((p = getpwent()) != NULL)
          (void) save_user(p->pw_name, p->pw_dir);
      (void) endpwent();
    }
#endif
    return NULL;
}

/*
 * Returns the home-directory as specified by environment variables.  This is
 * not necessarily what the passwd interface would say.
 */
char *
home_dir(void)
{
    char *result;
#if SYS_VMS
    if ((result = getenv("SYS$LOGIN")) == 0)
      result = getenv("HOME");
#else
    result = getenv("HOME");
#if SYS_WINNT
    if (result == 0) {
      if ((result = getenv("USERPROFILE")) == 0 &&
          (result = getenv("HOMESHARE")) == 0) {
          /* M$ copies from VMS, but does not learn from its mistakes... */
          if ((result = getenv("HOMEPATH")) != 0) {
            static char desktop[NFILEN];
            char *drive = getenv("HOMEDRIVE");
            result = pathcat(desktop, drive, result);
          }
      }
    }
#endif
#endif
    TRACE(("home_dir ->%s\n", TRACE_NULL(result)));
    return result;
}

/*
 * Expand a leading "~user" or "~/" on a given pathname.
 */
char *
home_path(char *path)
{
    if (*path == '~') {
      char temp[NFILEN];
      char *s;
      char *d;

      /* parse out the user-name portion */
      for (s = path + 1, d = temp; (*d = *s) != EOS; d++, s++) {
          if (is_slashc(*d)) {
            *d = EOS;
            s++;
            break;
          }
      }

#if OPT_VMS_PATH
      (void) mklower(temp);
#endif
      if ((d = find_user(temp)) != NULL) {
          (void) pathcat(path, d, s);
#if OPT_VMS_PATH
          /*
           * path name is now potentially in this form:
           *
           *    disk:[rooted_logical.][dir]subdir/leaf
           *    disk:[dir]subdir/leaf
           *            etc.
           *
           * which is not legit.  fix it up.
           */

          if ((s = strrchr(path, '/')) != NULL) {
            if ((d = strrchr(path, ']')) != NULL)
                *d = '.';
            *s = ']';
            d = path;
            while ((s = strchr(d, '/')) != NULL) {
                *s++ = '.';
                d = s;
            }
          }
#endif
      }
    }
    return path;
}
#endif

#ifndef HAVE_REALPATH
#ifdef GMDRESOLVE_LINKS
/*
 * Some of this code was "borrowed" from the GNU C library (getcwd.c).  It has
 * been largely rewritten and bears little resemblance to what it started out
 * as.
 *
 * The purpose of this code is to generalize getcwd.  The idea is to pass it as
 * input some path name.  This pathname can be relative, absolute, whatever.
 * It may have elements which reference symbolic links.  The output from this
 * function will be the absolute pathname representing the same file.
 * Actually, it only returns the directory.  If the thing you pass it is a
 * directory, you'll get that directory back (canonicalized).  If you pass it a
 * path to an ordinary file, you'll get back the canonicalized directory which
 * contains that file.
 *
 * The way that this code works is similar to the classic implementation of
 * getcwd (or getwd).  The difference is that once it finds a directory, it
 * will cache it.  If that directory is referenced again, finding it will be
 * very fast.  The caller of this function should not free up the pointer which
 * is returned.  This will be done automatically by the caching code.  The
 * value returned will exist at least up until the next call.  It should not be
 * relied on any longer than this.  Care should be taken not to corrupt the
 * value returned.
 *
 * FIXME: there should be some way to reset the cache in case directories are
 * renamed.
 */

#define CPN_CACHE_SIZE 64
#define CPN_CACHE_MASK (CPN_CACHE_SIZE-1)

#if !defined(HAVE_SETITIMER) || !defined(HAVE_SIGACTION)
#define TimedStat file_stat
#else /* !defined(HAVE_SETITIMER) */

#define TimedStat stat_with_timeout

static jmp_buf stat_jmp_buf;  /* for setjmp/longjmp on timeout */

static SIGT
StatHandler(int ACTUAL_SIG_ARGS)
{
    longjmp(stat_jmp_buf, signo);
    SIGRET;
}

static int
stat_with_timeout(const char *path, struct stat *statbuf)
{
    struct sigaction newact;
    struct sigaction oldact;
    sigset_t newset;
    sigset_t oldset;
    struct itimerval timeout;
    struct itimerval oldtimerval;
    int retval, stat_errno;

    newact.sa_handler = StatHandler;
    newact.sa_flags = 0;
    if (sigemptyset(&newact.sa_mask) < 0
      || sigfillset(&newset) < 0
      || sigdelset(&newset, SIGPROF) < 0
      || sigprocmask(SIG_BLOCK, &newset, &oldset) < 0)
      return -1;

    if (sigaction(SIGPROF, &newact, &oldact) < 0) {
      sigprocmask(SIG_SETMASK, &oldset, (sigset_t *) 0);
      return -1;
    }

    timeout.it_interval.tv_sec = 0;
    timeout.it_interval.tv_usec = 0;
    timeout.it_value.tv_sec = 0;
    timeout.it_value.tv_usec = 75000;

    (void) setitimer(ITIMER_PROF, &timeout, &oldtimerval);

    /*
     * POSIX says that 'stat()' won't return an error if it's interrupted,
     * so we force an error by making a longjmp from the timeout handler,
     * and forcing the error return status.
     */
    if (setjmp(stat_jmp_buf)) {
      retval = -1;
      stat_errno = EINTR;
    } else {
      retval = file_stat(path, statbuf);
      stat_errno = errno;
    }

    timeout.it_value.tv_usec = 0;
    (void) setitimer(ITIMER_PROF, &timeout, (struct itimerval *) 0);

    (void) sigaction(SIGPROF, &oldact, (struct sigaction *) 0);
    (void) sigprocmask(SIG_SETMASK, &oldset, (sigset_t *) 0);
    (void) setitimer(ITIMER_PROF, &oldtimerval, (struct itimerval *) 0);

    errno = stat_errno;
    return retval;
}
#endif /* !defined(HAVE_SETITIMER) */

static char *
resolve_directory(char *path_name, char **file_namep)
{
    dev_t rootdev, thisdev;
    ino_t rootino, thisino;
    struct stat st;

    static const char rootdir[] =
    {SLASHC, EOS};

    static TBUFF *last_leaf;
    static TBUFF *last_path;
    static TBUFF *last_temp;

    char *temp_name;
    char *tnp;                /* temp name pointer */
    char *temp_path;          /* the path that we've determined */

    size_t len;               /* temporary for length computations */

    static struct cpn_cache {
      dev_t ce_dev;
      ino_t ce_ino;
      TBUFF *ce_dirname;
    } cache_entries[CPN_CACHE_SIZE];

    struct cpn_cache *cachep;

    tb_free(&last_leaf);
    *file_namep = NULL;
    len = strlen(path_name);

    if (!tb_alloc(&last_temp, len + 1))
      return NULL;
    tnp = (temp_name = tb_values(last_temp)) + len;

    if (!tb_alloc(&last_path, len + 1))
      return NULL;
    *(temp_path = tb_values(last_path)) = EOS;

    (void) strcpy(temp_name, path_name);

    /*
     * Test if the given pathname is an actual directory, or not.  If it's
     * a symbolic link, we'll have to determine if it points to a directory
     * before deciding how to split it.
     */
    if (lstat(temp_name, &st) < 0)
      st.st_mode = S_IFREG;   /* assume we're making a file... */

    if (!S_ISDIR(st.st_mode)) {
      int levels = 0;
      char target[NFILEN];

      /* loop until no more links */
      while ((st.st_mode & S_IFMT) == S_IFLNK) {
          int got = 0;

          if (levels++ > 4    /* FIXME */
            || (got = readlink(temp_name,
                           target, sizeof(target) - 1)) < 0) {
            return NULL;
          }
          target[got] = EOS;

          if (tb_alloc(&last_temp,
                   (size_t) (strlen(temp_name) + got + 1)) == 0)
            return NULL;

          temp_name = tb_values(last_temp);

          if (!is_slashc(target[0])) {
            tnp = pathleaf(temp_name);
            if (tnp != temp_name && !is_slashc(tnp[-1]))
                *tnp++ = SLASHC;
            (void) strcpy(tnp, target);
          } else {
            (void) strcpy(temp_name, target);
          }
          if (lstat(temp_name, &st) < 0)
            break;
      }

      /*
       * If we didn't resolve any symbolic links, we can find the
       * filename leaf in the original 'path_name' argument.
       */
      tnp = last_slash(temp_name);
      if (tnp == NULL) {
          tnp = temp_name;
          if (tb_scopy(&last_leaf, tnp) == 0)
            return NULL;
          *tnp++ = '.';
          *tnp = EOS;
      } else if (tb_scopy(&last_leaf, tnp + 1) == 0) {
          return NULL;
      }
      if (tnp == temp_name && is_slashc(*tnp))  /* initial slash */
          tnp++;
      *tnp = EOS;

      /*
       * If the parent of the given path_name isn't a directory, give
       * up...
       */
      if (TimedStat(temp_name, &st) < 0 || !S_ISDIR(st.st_mode))
          return NULL;
    }

    /*
     * Now, 'temp_name[]' contains a null-terminated directory-path, and
     * 'tnp' points to the null.  If we've set file_namep, we've allocated
     * a pointer since it may be pointing within the temp_name string --
     * which may be overwritten.
     */
    *file_namep = tb_values(last_leaf);

    thisdev = st.st_dev;
    thisino = st.st_ino;

    cachep = &cache_entries[(thisdev ^ thisino) & CPN_CACHE_MASK];
    if (tb_values(cachep->ce_dirname) != 0
      && cachep->ce_ino == thisino
      && cachep->ce_dev == thisdev) {
      return tb_values(cachep->ce_dirname);
    } else {
      cachep->ce_ino = thisino;
      cachep->ce_dev = thisdev;
      tb_free(&(cachep->ce_dirname));           /* will reset iff ok */
    }

    if (TimedStat(rootdir, &st) < 0)
      return NULL;

    rootdev = st.st_dev;
    rootino = st.st_ino;

    while ((thisdev != rootdev)
         || (thisino != rootino)) {
      register DIR *dp;
      register DIRENT *de;
      dev_t dotdev;
      ino_t dotino;
      char mount_point;
      size_t namelen = 0;

      len = tnp - temp_name;
      if (tb_alloc(&last_temp, 4 + len) == 0)
          return NULL;

      tnp = (temp_name = tb_values(last_temp)) + len;
      *tnp++ = SLASHC;
      *tnp++ = '.';
      *tnp++ = '.';
      *tnp = EOS;

      /* Figure out if this directory is a mount point.  */
      if (TimedStat(temp_name, &st) < 0)
          return NULL;

      dotdev = st.st_dev;
      dotino = st.st_ino;
      mount_point = (dotdev != thisdev);

      /* Search for the last directory.  */
      if ((dp = opendir(temp_name)) != 0) {
          int found = FALSE;

          while ((de = readdir(dp)) != NULL) {
#if USE_D_NAMLEN
            namelen = de->d_namlen;
#else
            namelen = strlen(de->d_name);
#endif
            /* Ignore "." and ".." */
            if (de->d_name[0] == '.'
                && (namelen == 1
                  || (namelen == 2 && de->d_name[1] == '.')))
                continue;

            if (mount_point
                || ((long) de->d_ino == (long) thisino)) {
                len = tnp - temp_name;
                if (tb_alloc(&last_temp, len + namelen + 1) == 0)
                  break;

                temp_name = tb_values(last_temp);
                tnp = temp_name + len;

                *tnp = SLASHC;
                (void) strncpy(tnp + 1, de->d_name, namelen);
                tnp[namelen + 1] = EOS;

                if (TimedStat(temp_name, &st) == 0
                  && st.st_dev == thisdev
                  && st.st_ino == thisino) {
                  found = TRUE;
                  break;
                }
            }
          }

          if (found) {
            /*
             * Push the latest directory-leaf before the
             * string already in 'temp_path[]'.
             */
            len = strlen(temp_path) + 1;
            if (tb_alloc(&last_path, len + namelen + 1) == 0) {
                (void) closedir(dp);
                return NULL;
            }
            temp_path = tb_values(last_path);
            while (len-- != 0)
                temp_path[namelen + 1 + len] = temp_path[len];
            temp_path[0] = SLASHC;
            (void) memcpy(temp_path + 1, de->d_name, namelen);
          }
          (void) closedir(dp);
          if (!found)
            return NULL;
      } else                  /* could't open directory */
          return NULL;

      thisdev = dotdev;
      thisino = dotino;
    }

    if (tb_scopy(&(cachep->ce_dirname),
             *temp_path ? temp_path : rootdir) == 0)
      return NULL;

    return tb_values(cachep->ce_dirname);
}
#endif /* defined(GMDRESOLVE_LINKS) */
#endif /* defined(HAVE_REALPATH) */

#if OPT_CASELESS

/*
 * The function case_correct_path is intended to determine the true
 * case of all pathname components of a syntactically canonicalized
 * pathname for operating systems on which OPT_CASELESS applies.
 */

#if SYS_WINNT

static int
case_correct_path(char *old_file, char *new_file)
{
    WIN32_FIND_DATA fd;
    HANDLE h;
    int len;
    char *next, *current, *end, *sofar;
    char tmp_file[MAX_PATH];

    /* Handle old_file == new_file safely. */
    (void) vl_strncpy(tmp_file, old_file, sizeof(tmp_file));
    old_file = tmp_file;

    if (is_slashc(old_file[0]) && is_slashc(old_file[1])) {

      /* Handle UNC filenames. */
      current = old_file + 2;
      next = strchr(current, SLASHC);
      if (next)
          next = strchr(next + 1, SLASHC);

      /* Canonicalize the system name and share name. */
      if (next)
          len = next - old_file + 1;
      else
          len = strlen(old_file);
      (void) memcpy(new_file, old_file, len);
      new_file[len] = EOS;
      (void) mklower(new_file);
      if (!next)
          return 0;
      sofar = new_file + len;
      current = next + 1;
    } else {

      /* Canonicalize a leading drive letter, if any. */
      if (old_file[0] && old_file[1] == ':') {
          new_file[0] = old_file[0];
          new_file[1] = old_file[1];
          if (isLower(new_file[0]))
            new_file[0] = (char) toUpper(new_file[0]);
          current = old_file + 2;
          sofar = new_file + 2;
      } else {
          current = old_file;
          sofar = new_file;
      }

      /* Skip a leading slash, if any. */
      if (is_slashc(*current)) {
          current++;
          *sofar++ = SLASHC;
      }
    }

    /* Canonicalize each pathname prefix. */
    end = skip_string(old_file);
    while (current < end) {
      next = strchr(current, SLASHC);
      if (!next)
          next = end;
      len = next - current;
      (void) memcpy(sofar, current, len);
      sofar[len] = EOS;
      h = FindFirstFile(new_file, &fd);
      if (h != INVALID_HANDLE_VALUE) {
          FindClose(h);
          (void) strcpy(sofar, fd.cFileName);
          sofar += strlen(sofar);
      } else
          sofar += len;
      if (next != end)
          *sofar++ = SLASHC;
      current = next + 1;
    }
    return 0;
}

#else /* !SYS_WINNT */

#if SYS_OS2

int
is_case_preserving(const char *name)
{
    int case_preserving = 1;

    /* Determine if the filesystem is case-preserving. */
    if (name[0] && name[1] == ':') {
      char drive_name[3];
      char buffer[sizeof(FSQBUFFER2) + 3 * CCHMAXPATH];
      FSQBUFFER2 *pbuffer = (FSQBUFFER2 *) buffer;
      ULONG len = sizeof(buffer);
      APIRET rc;

      drive_name[0] = name[0];
      drive_name[1] = name[1];
      drive_name[2] = EOS;
      rc = DosQueryFSAttach(drive_name, 0, FSAIL_QUERYNAME,
                        pbuffer, &len);
      if (rc == NO_ERROR) {
          char *name = pbuffer->szName + pbuffer->cbName + 1;

          if (strcmp(name, "FAT") == 0)
            case_preserving = 0;
      }
    }
    return case_preserving;
}

static int
case_correct_path(char *old_file, char *new_file)
{
    FILEFINDBUF3 fb;
    ULONG entries;
    HDIR hdir;
    APIRET rc;
    char *next, *current, *end, *sofar;
    char tmp_file[NFILEN + 2];
    ULONG len;
    int case_preserving = is_case_preserving(old_file);

    /* Handle old_file == new_file safely. */
    (void) strcpy(tmp_file, old_file);
    old_file = tmp_file;

    /* If it isn't case-preserving then just down-case it. */
    if (!case_preserving) {
      (void) mklower(strcpy(new_file, old_file));
      return 0;
    }

    /* Canonicalize a leading drive letter, if any. */
    if (old_file[0] && old_file[1] == ':') {
      new_file[0] = old_file[0];
      new_file[1] = old_file[1];
      if (isLower(new_file[0]))
          new_file[0] = toUpper(new_file[0]);
      current = old_file + 2;
      sofar = new_file + 2;
    } else {
      current = old_file;
      sofar = new_file;
    }

    /* Skip a leading slash, if any. */
    if (is_slashc(*current)) {
      current++;
      *sofar++ = SLASHC;
    }

    /* Canonicalize each pathname prefix. */
    end = skip_string(old_file);
    while (current < end) {
      next = strchr(current, SLASHC);
      if (!next)
          next = end;
      len = next - current;
      (void) memcpy(sofar, current, len);
      sofar[len] = EOS;
      hdir = HDIR_CREATE;
      entries = 1;
      rc = DosFindFirst(new_file, &hdir,
                    FILE_DIRECTORY | FILE_READONLY, &fb, sizeof(fb),
                    &entries, FIL_STANDARD);
      if (rc == NO_ERROR) {
          DosFindClose(hdir);
          (void) strcpy(sofar, fb.achName);
          sofar += strlen(sofar);
      } else
          sofar += len;
      if (next != end)
          *sofar++ = SLASHC;
      current = next + 1;
    }
    return 0;
}

#else /* !SYS_OS2 */

static int
case_correct_path(char *old_file, char *new_file)
{
    if (old_file != new_file)
      (void) strcpy(new_file, old_file);
    return 0;
}

#endif /* !SYS_OS2 */

#endif /* !SYS_WINNT */

#endif /* OPT_CASELESS */

/* canonicalize a pathname, to eliminate extraneous /./, /../, and ////
      sequences.  only guaranteed to work for absolute pathnames */
static char *
canonpath(char *ss)
{
    char *p, *pp;
    char *s;

    TRACE((T_CALLED "canonpath '%s'\n", str_visible(ss)));
    if ((s = is_appendname(ss)) != 0) {
      returnString((canonpath(s) != 0) ? ss : 0);
    } else {

      s = ss;

      if (!*s)
          returnString(s);

#if OPT_MSDOS_PATH
#if !OPT_CASELESS
      (void) mklower(ss);     /* MS-DOS is case-independent */
#endif
      if (is_slashc(*ss))
          *ss = SLASHC;
      /* pretend the drive designator isn't there */
      if ((s = is_msdos_drive(ss)) == 0)
          s = ss;
#endif

#if SYS_UNIX
      (void) home_path(s);
#endif

#if OPT_VMS_PATH
      /*
       * If the code in 'lengthen_path()', as well as the scattered calls on
       * 'fgetname()' are correct, the path given to this procedure should
       * be a fully-resolved VMS pathname.  The logic in filec.c will allow a
       * unix-style name, so we'll fall-thru if we find one.
       */
      if (is_vms_pathname(s, -TRUE)) {
          returnString(mkupper(ss));
      }
#endif

#if SYS_UNIX || OPT_MSDOS_PATH || OPT_VMS_PATH
      if (!is_slashc(*s)) {
          mlforce("BUG: canonpath '%s'", s);
          returnString(ss);
      }
      *s = SLASHC;

      /*
       * If the system supports symbolic links (most UNIX systems do), we
       * cannot do dead reckoning to resolve the pathname.  We've made this a
       * user-mode because some systems have problems with NFS timeouts which
       * can make running vile _slow_.
       */
#ifdef GMDRESOLVE_LINKS
      if (global_g_val(GMDRESOLVE_LINKS)) {
          char *leaf;
          char temp[NFILEN];
#ifdef HAVE_REALPATH
          char temp2[NFILEN];
          char temp3[NFILEN];
          char *real = realpath(s, temp);

          if (real != 0) {
            (void) strcpy(s, real);
          } else if ((leaf = pathleaf(strcpy(temp, s))) != 0) {
            strcpy(temp2, leaf);
            if (leaf == temp + 1)
                leaf[0] = EOS;
            else
                leaf[-1] = EOS;
            if (realpath(temp, temp3) != 0) {
                pathcat(s, temp3, temp2);
            }
          }
#else
          char *head = resolve_directory(s, &leaf);
          if (head != 0) {
            if (leaf != 0)
                (void) strcpy(s, pathcat(temp, head, leaf));
            else
                (void) strcpy(s, head);
          }
#endif
      } else
#endif
      {
          p = pp = s;

          p++;
          pp++;         /* leave the leading slash */
          while (*pp) {
            if (is_slashc(*pp)) {
                pp++;
                continue;
            }
            if (*pp == '.' && is_slashc(*(pp + 1))) {
                pp += 2;
                continue;
            }
            break;
          }
          while (*pp) {
            if (is_slashc(*pp)) {
                while (is_slashc(*(pp + 1)))
                  pp++;
                if (p > s && !is_slashc(*(p - 1)))
                  *p++ = SLASHC;
                if (*(pp + 1) == '.') {
                  if (*(pp + 2) == EOS) {
                      /* change "/." at end to "" */
                      *(p - 1) = EOS;     /* and we're done */
                      break;
                  }
                  if (is_slashc(*(pp + 2))) {
                      pp += 2;
                      continue;
                  } else if (*(pp + 2) == '.' && (is_slashc(*(pp + 3))
                                          || *(pp + 3) == EOS)) {
                      while (p - 1 > s && is_slashc(*(p - 1)))
                        p--;
                      while (p > s && !is_slashc(*(p - 1)))
                        p--;
                      if (p == s)
                        *p++ = SLASHC;
                      pp += 3;
                      continue;
                  }
                }
                pp++;
                continue;
            } else {
                *p++ = *pp++;
            }
          }
          if (p > s && is_slashc(*(p - 1)))
            p--;
          if (p == s)
            *p++ = SLASHC;
          *p = EOS;
      }
#endif /* SYS_UNIX || SYS_MSDOS */

#if OPT_VMS_PATH
      if (!is_vms_pathname(ss, -TRUE)) {
          char *tt = skip_string(ss);

          /*
           * If we're not looking at "/" or some other path that ends
           * with a slash, see if we can match the path to a directory
           * file.  If so, force a slash on the end so that the unix2vms
           * conversion will show a directory.
           */
          if (tt[-1] != SLASHC) {
            struct stat sb;
#if SYS_VMS
            (void) strcpy(tt, ".DIR");
#else
            (void) mklower(ss);
#endif
            if ((file_stat(ss, &sb) >= 0)
                && S_ISDIR(sb.st_mode))
                (void) strcpy(tt, "/");
            else
                *tt = EOS;
          }

          /* FIXME: this is a hack to prevent this function from
           * returning device-level strings, since (at the moment) I
           * don't have anything that returns a list of the mounted
           * devices on a VMS system.
           */
          if (!strcmp(ss, "/")) {
            (void) strcpy(ss, current_directory(FALSE));
            if ((tt = strchr(ss, ':')) != 0)
                (void) strcpy(tt + 1, "[000000]");
            else
                (void) strcat(ss, ":");
            (void) mkupper(ss);
          } else {
            unix2vms_path(ss, ss);
          }
      }
#endif

#if OPT_CASELESS
      case_correct_path(ss, ss);
#endif
    }
    returnString(ss);
}

char *
shorten_path(char *path, int keep_cwd)
{
    char temp[NFILEN];
    char *cwd;
    char *ff;
    char *slp;
    char *f;
#if OPT_VMS_PATH
    char *dot;
#else
# if SYS_UNIX || OPT_MSDOS_PATH
    int found;
# endif
#endif

    if (!path || *path == EOS)
      return path;

    if (isInternalName(path))
      return path;

    if ((f = is_appendname(path)) != 0)
      return (shorten_path(f, keep_cwd) != 0) ? path : 0;

    TRACE(("shorten '%s'\n", path));
#if OPT_VMS_PATH
    /*
     * This assumes that 'path' is in canonical form.
     */
    cwd = current_directory(FALSE);
    ff = path;
    dot = 0;
    TRACE(("current '%s'\n", cwd));

    if ((slp = strchr(cwd, '[')) != 0
      && (slp == cwd
          || !strncmp(cwd, path, (size_t) (slp - cwd)))) {  /* same device? */
      ff += (slp - cwd);
      cwd = slp;
      (void) strcpy(temp, "[");     /* hoping for relative-path */
      while (*cwd && *ff) {
          if (*cwd != *ff) {
            if (*cwd == ']' && *ff == '.') {
                /* "[.DIRNAME]FILENAME.TYP;1" */
                ;
            } else if (*cwd == '.' && *ff == ']') {
                /* "[-]FILENAME.TYP;1" */
                while (*cwd != EOS) {
                  if (*cwd++ == '.')
                      (void) strcat(temp, "-");
                }
                (void) strcat(temp, "]");
                ff++;
            } else if (dot != 0) {
                int diff = (ff - dot);

                /* "[-.DIRNAME]FILENAME.TYP;1" */
                while (*cwd != EOS) {
                  if (*cwd++ == '.')
                      (void) strcat(temp, "-");
                }
                while (dot != ff) {
                  if (*dot++ == '.')
                      (void) strcat(temp, "-");
                }
                (void) strcat(temp, ".");
                ff -= (diff - 1);
            }
            break;
          } else if (*cwd == ']') {
            (void) strcat(temp, cwd);
            ff++;       /* path-leaf, if any */
            break;
          }

          if (*ff == '.')
            dot = ff;
          cwd++;
          ff++;
      }
    } else {
      *temp = EOS;            /* different device, cannot relate */
    }

    if (!strcmp(temp, "[]")   /* "[]FILENAME.TYP;1" */
      &&!keep_cwd)
      *temp = EOS;

    (void) strcpy(path, strcat(temp, ff));
#else
# if SYS_UNIX || OPT_MSDOS_PATH
    cwd = current_directory(FALSE);
    slp = ff = path;
    while (*cwd && *ff && *cwd == *ff) {
      if (is_slashc(*ff))
          slp = ff;
      cwd++;
      ff++;
    }

    /* if we reached the end of cwd, and we're at a path boundary,
       then the file must be under '.' */
    found = FALSE;
    if (*cwd == EOS) {
      if (keep_cwd) {
          temp[0] = '.';
          temp[1] = SLASHC;
          temp[2] = EOS;
      } else
          *temp = EOS;
      if (is_slashc(*ff)) {
          found = TRUE;
          (void) strcpy(path, strcat(temp, ff + 1));
      } else if (slp == ff - 1) {
          found = TRUE;
          (void) strcpy(path, strcat(temp, ff));
      }
    }

    if (!found) {
      /* if we mismatched during the first path component, we're done */
      if (slp != path) {
          /* if we mismatched in the last component of cwd, then the file
             is under '..' */
          if (last_slash(cwd) == 0)
            (void) strcpy(path, strcat(strcpy(temp, ".."), slp));
      }
    }

    /* we're off by more than just '..', so use absolute path */
# endif                       /* SYS_UNIX || SYS_MSDOS */
#endif /* OPT_VMS_PATH */

    TRACE(("     -> '%s' shorten\n", path));
    return path;
}

#if OPT_VMS_PATH
static int
mixed_case(const char *path)
{
    register int c;
    int had_upper = FALSE;
    int had_lower = FALSE;
    while ((c = *path++) != EOS) {
      if (isLower(c))
          had_lower = TRUE;
      if (isUpper(c))
          had_upper = TRUE;
    }
    return (had_upper && had_lower);
}
#endif

/*
 * Undo nominal effect of 'shorten_path()'
 */
char *
lengthen_path(char *path)
{
#if SYS_VMS
    struct FAB my_fab;
    struct NAM my_nam;
    char my_esa[NAM$C_MAXRSS];      /* expanded: sys$parse */
    char my_rsa[NAM$C_MAXRSS];      /* result: sys$search */
#endif
    register int len;
    const char *cwd;
    char *f;
    char temp[NFILEN];
#if OPT_MSDOS_PATH
    char drive;
#endif

    if ((f = is_appendname(path)) != 0)
      return (lengthen_path(f) != 0) ? path : 0;

    if ((f = path) == 0
      || (f == error_val))
      return path;

    if (*path != EOS && isInternalName(path)) {
#if OPT_VMS_PATH
      /*
       * The conflict between VMS pathnames (e.g., "[-]") and Vile's
       * scratch-buffer names is a little ambiguous.  On VMS, though,
       * we'll have to give VMS pathnames the edge.  We cheat a little,
       * by exploiting the fact (?) that the system calls return paths
       * in uppercase only.
       */
      if (!is_vms_pathname(path, TRUE) && !mixed_case(path))
#endif
          return path;
    }
#if SYS_UNIX
    (void) home_path(f);
#endif

#if SYS_VMS
    /*
     * If the file exists, we can ask VMS to tell the full pathname.
     */
    if ((*path != EOS) && maybe_pathname(path)) {
      int fd;
      long status;
      char temp[NFILEN], leaf[NFILEN];
      register char *s;

      if (!strchr(path, '*') && !strchr(path, '?')) {
          if ((fd = open(SL_TO_BSL(path), O_RDONLY, 0)) >= 0) {
            getname(fd, temp);
            (void) close(fd);
            return strcpy(path, temp);
          }
      }

      /*
       * Path either contains a wildcard, or the file does
       * not already exist.  Use the system parser to expand
       * the pathname components.
       */
      my_fab = cc$rms_fab;
      my_fab.fab$l_fop = FAB$M_NAM;
      my_fab.fab$l_nam = &my_nam;   /* FAB => NAM block     */
      my_fab.fab$l_dna = "";  /* Default-selection    */
      my_fab.fab$b_dns = strlen(my_fab.fab$l_dna);

      my_fab.fab$l_fna = path;
      my_fab.fab$b_fns = strlen(path);

      my_nam = cc$rms_nam;
      my_nam.nam$b_ess = NAM$C_MAXRSS;
      my_nam.nam$l_esa = my_esa;
      my_nam.nam$b_rss = NAM$C_MAXRSS;
      my_nam.nam$l_rsa = my_rsa;

      if ((status = sys$parse(&my_fab)) == RMS$_NORMAL) {
          char *s = my_esa;
          int len = my_nam.nam$b_esl;
          s[len] = EOS;
          if (len > 2) {
            s = pathleaf(s);
            if (!strcmp(s, ".;"))
                *s = EOS;
          }
          return strcpy(path, my_esa);
      } else {
          /* FIXME: try to expand partial directory specs, etc. */
      }
    }
#else
# if OPT_VMS_PATH
    /* this is only for testing! */
    if (fakevms_filename(path))
      return path;
# endif
#endif

#if SYS_UNIX || OPT_MSDOS_PATH || OPT_VMS_PATH
#if OPT_MSDOS_PATH
    if ((f = is_msdos_drive(path)) != 0) {
      drive = *path;
    } else {
      drive = EOS;
      f = path;
    }
#endif
    if (!is_slashc(f[0])) {
#if OPT_MSDOS_PATH

#if OPT_UNC_PATH
      if (drive == EOS) {
          GetCurrentDirectory(sizeof(temp), temp);
          bsl_to_sl_inplace(temp);
          cwd = temp;
      } else
#endif
          cwd = curr_dir_on_drive(drive != EOS
                            ? drive
                            : curdrive());

      if (!cwd) {
          /* Drive will be unspecified with UNC Paths */
          if ((temp[0] = drive) != EOS) {
            (void) strcpy(temp + 1, ":/");
          }
          cwd = temp;
      }
#else
      cwd = current_directory(FALSE);
      if (!is_slashc(*cwd))
          return path;
#endif
#if OPT_VMS_PATH
      vms2unix_path(temp, cwd);
#else
      (void) strcpy(temp, cwd);
#endif
      len = strlen(temp);
      temp[len++] = SLASHC;
      (void) vl_strncpy(temp + len, f, sizeof(temp) - len);
      (void) strcpy(path, temp);
    }
#if OPT_MSDOS_PATH
    if (is_msdos_drive(path) == 0) {      /* ensure that we have drive too */
      /* UNC paths have no drive */
      if (curdrive() != 0) {
          temp[0] = (char) curdrive();
          temp[1] = ':';
          (void) vl_strncpy(temp + 2, path, sizeof(temp) - 2);
          (void) strcpy(path, temp);
      }
    }
#endif
#endif /* SYS_UNIX || SYS_MSDOS */

    return canonpath(path);
}

/*
 * Returns true if the argument looks like an absolute pathname (e.g., on
 * unix, begins with a '/').
 */
static int
is_absolute_pathname(char *path)
{
    char *f;
    if ((f = is_appendname(path)) != 0)
      return is_pathname(f);

#if OPT_VMS_PATH
    if (is_vms_pathname(path, -TRUE)
      && (strchr(path, L_BLOCK) != 0
          || strchr(path, ':') != 0))
      return TRUE;
#endif

#if OPT_MSDOS_PATH
    if ((f = is_msdos_drive(path)) != 0)
      return is_absolute_pathname(f);
#endif

#if SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS
#if SYS_UNIX
    if (path[0] == '~')
      return TRUE;
#endif
    if (is_slashc(path[0]))
      return TRUE;
#endif /* SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS */

    return FALSE;
}

/*
 * Returns true if the argument looks like a relative pathname (e.g., on
 * unix, begins with "./" or "../")
 */
static int
is_relative_pathname(const char *path)
{
    int n;
#if OPT_VMS_PATH
    if (is_vms_pathname(path, -TRUE)
      && !strncmp(path, "[-", 2))
      return TRUE;
#endif
#if SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS
    n = 0;
    if (path[n++] == '.') {
      if (path[n] == '.')
          n++;
      if (is_slashc(path[n]))
          return TRUE;
    }
#endif /* SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS */

    return FALSE;
}

/*
 * Returns true if the argument looks more like a pathname than anything else.
 *
 * Notes:
 *    This makes a syntax-only test (e.g., at the beginning of the string).
 *    VMS can accept UNIX-style /-delimited pathnames.
 */
int
is_pathname(char *path)
{
    return is_relative_pathname(path)
      || is_absolute_pathname(path);
}

/*
 * A bit weaker than 'is_pathname()', checks to see if the string contains
 * path delimiters.
 */
int
maybe_pathname(char *fn)
{
    if (is_pathname(fn))      /* test the obvious stuff */
      return TRUE;
#if OPT_MSDOS_PATH
    if (is_msdos_drive(fn))
      return TRUE;
#endif
    if (last_slash(fn) != 0)
      return TRUE;
#if OPT_VMS_PATH
    while (*fn != EOS) {
      if (ispath(*fn) && !isident(*fn))
          return TRUE;
      fn++;
    }
#endif
    return FALSE;
}

/*
 * Returns the filename portion if the argument is an append-name (and not an
 * internal name!), otherwise null.
 */
char *
is_appendname(char *fn)
{
    if (fn != 0) {
      if (isAppendToName(fn)) {
          fn += 2;            /* skip the ">>" prefix */
          fn = skip_blanks(fn);
          if (!isInternalName(fn))
            return fn;
      }
    }
    return 0;
}

/*
 * Returns true if the filename is either a scratch-name, or is the string that
 * we generate for the filename-field of [Help] and [Buffer List].  Use this
 * function rather than simple tests of '[' to make tests for VMS filenames
 * unambiguous.
 */
int
is_internalname(const char *fn)
{
#if OPT_VMS_PATH
    if (is_vms_pathname(fn, FALSE))
      return FALSE;
#endif
    if (!strcmp(fn, non_filename()))
      return TRUE;
    return (*fn == EOS) || is_scratchname(fn);
}

/*
 * Make the simple test only for bracketed name.  We only use this when we're
 * certain it's a buffer name.
 */
int
is_scratchname(const char *fn)
{
    return ((*fn == SCRTCH_LEFT[0]) && (fn[strlen(fn) - 1] == SCRTCH_RIGHT[0]));
}

/*
 * Test if the given path is a directory
 */
int
is_directory(char *path)
{
#if OPT_VMS_PATH
    register char *s;
#endif
    struct stat sb;

    if (path == NULL || *path == EOS)
      return FALSE;

#if OPT_VMS_PATH
    if (is_vms_pathname(path, TRUE)) {
      return TRUE;
    }

    /* If the name doesn't look like a directory, there's no point in
     * wasting time doing a 'stat()' call.
     */
    s = vms_pathleaf(path);
    if ((s = strchr(s, '.')) != 0) {
      char ftype[NFILEN];
      (void) mkupper(strcpy(ftype, s));
      if (strcmp(ftype, ".DIR")
          && strcmp(ftype, ".DIR;1"))
          return FALSE;
    }
#endif
#if OPT_UNC_PATH
    /*
     * WARNING: kludge alert!
     *
     * The problem here is that \\system\share, if it exists,
     * must be a directory.  However, due to a bug in the win32
     * stat function, it may be reported to exist (stat succeeds)
     * but that it is a file, not a directory.  So we special case
     * a stand-alone \\system\share name and force it to be reported
     * as a dir.
     */
    if (is_slashc(path[0]) && is_slashc(path[1])) {
      char *end = skip_string(path);
      int slashes = 0;
      if (end > path && is_slashc(end[-1]))
          end--;
      while (--end >= path) {
          if (is_slashc(*end))
            slashes++;
      }
      if (slashes == 3)
          return 1;
    }
#endif
    return ((file_stat(path, &sb) >= 0) && S_ISDIR(sb.st_mode));
}

static int
is_filemode(struct stat *sb)
{
    return (S_ISREG(sb->st_mode) || S_ISFIFO(sb->st_mode));
}

/*
 * Test if the given path is a regular file, i.e., one that we might open.
 */
int
is_file(char *path)
{
    struct stat sb;

    return ((file_stat(path, &sb) >= 0) && is_filemode(&sb));
}

/*
 * Test if the given path is not a regular file, i.e., one that we won't open.
 */
int
is_nonfile(char *path)
{
    struct stat sb;

    return ((file_stat(path, &sb) >= 0) && !is_filemode(&sb));
}

/*
 * Parse the next entry in a list of pathnames, returning null only when no
 * more entries can be parsed.
 */
const char *
parse_pathlist(const char *list, char *result)
{
    if (list != NULL && *list != EOS) {
      register int len = 0;

      while (*list && (*list != vl_pathchr)) {
          if (len < NFILEN - 1)
            result[len++] = *list;
          list++;
      }
      if (len == 0)           /* avoid returning an empty-string */
          result[len++] = '.';
      result[len] = EOS;

      if (*list == vl_pathchr)
          ++list;
    } else
      list = NULL;
    return list;
}

#if SYS_WINNT && !CC_TURBO
/********                                               \\  opendir  //
 *                                                        ===========
 * opendir
 *
 * Description:
 *      Prepares to scan the file name entries in a directory.
 *
 * Arguments:   filename in NT format
 *
 * Returns:     pointer to a (malloc-ed) DIR structure.
 *
 * Joseph E. Greer      July 22 1992
 *
 ********/

DIR *
opendir(char *fname)
{
    char buf[MAX_PATH];
    DIR *od;

    (void) vl_strncpy(buf, fname, sizeof(buf));

    if (!strcmp(buf, "."))    /* if its just a '.', replace with '*.*' */
      (void) strcpy(buf, "*.*");
    else {
      /* If the name ends with a slash, append '*.*' otherwise '\*.*' */
      if (is_slashc(buf[strlen(buf) - 1]))
          (void) strcat(buf, "*.*");
      else
          (void) strcat(buf, "\\*.*");
    }

    /* allocate the structure to maintain currency */
    if ((od = typealloc(DIR)) == NULL)
      return NULL;

    /* Let's try to find a file matching the given name */
    if ((od->hFindFile = FindFirstFile(buf, &od->ffd))
      == INVALID_HANDLE_VALUE) {
      free(od);
      return NULL;
    }
    od->first = 1;
    return od;
}

/********                                               \\  readdir  //
 *                                                        ===========
 * readdir
 *
 * Description:
 *      Read a directory entry.
 *
 * Arguments:   a DIR pointer
 *
 * Returns:     A struct direct
 *
 * Joseph E. Greer      July 22 1992
 *
 ********/
DIRENT *
readdir(DIR * dirp)
{
    if (dirp->first)
      dirp->first = 0;
    else if (!FindNextFile(dirp->hFindFile, &dirp->ffd))
      return NULL;
    dirp->de.d_name = dirp->ffd.cFileName;
    return &dirp->de;
}

/********                                               \\  closedir  //
 *                                                        ===========
 * closedir
 *
 * Description:
 *      Close a directory entry.
 *
 * Arguments:   a DIR pointer
 *
 * Returns:     A struct direct
 *
 * Joseph E. Greer      July 22 1992
 *
 ********/
int
closedir(DIR * dirp)
{
    FindClose(dirp->hFindFile);
    free(dirp);
    return 0;
}

#endif /* SYS_WINNT */

#if OPT_MSDOS_PATH && !SYS_OS2_EMX
static char *
slconv(const char *f, char *t, char oc, char nc)
{
    char *retp = t;

    if (t != 0) {
      while (*f) {
          if (*f == oc)
            *t = nc;
          else
            *t = *f;
          f++;
          t++;
      }
      *t = EOS;
    }

    return retp;
}

/*
 * Use this function to filter our internal '/' format pathnames to '\'
 * when invoking system calls (e.g., opendir, chdir).
 */
char *
sl_to_bsl(const char *p)
{
    if (p != 0) {
      static TBUFF *cnv;
      size_t len = strlen(p);
      char *t = tb_values(tb_alloc(&cnv, (len | 127) + 1));
      char *s = slconv(p, t, '/', '\\');
      if ((s = is_msdos_drive(s)) == 0)
          s = t;
      /* Trim trailing slash if it's not the first */
      if ((len = strlen(s)) > 1
          && is_slashc(s[len - 1]))
          s[--len] = EOS;
      return t;
    }
    return 0;
}

/*
 * Use this function to tidy up and put the path-slashes into internal form.
 */
#ifndef bsl_to_sl_inplace
void
bsl_to_sl_inplace(char *p)
{
    (void) slconv(p, p, '\\', '/');
}
#endif

#endif /* OPT_MSDOS_PATH && !SYS_OS2_EMX */

#if OPT_VMS_PATH
/*
 * Locate the version in a VMS filename.  Usually it is the ';', but a '.' also
 * is accepted.  However, a '.' may appear in the directory part, or as the
 * suffix delimiter.
 */
char *
version_of(char *fname)
{
    char *s = strchr(fname, ';');
    if (s == 0) {
      fname = pathleaf(fname);
      if ((s = strchr(fname, '.')) == 0
          || (*++s == EOS)
          || (s = strchr(fname, '.')) == 0)
          s = skip_string(fname);
      if (strcmp(s, "*")) {   /* either "*" or a number */
          char *d;
          (void) strtol(s, &d, 10);
          if (*d != EOS)
            s = skip_string(s);
      }
    }
    return s;
}

/*
 * Strip the VMS version number, so the resulting path implicitly applies to
 * the current version.
 */
char *
strip_version(char *path)
{
    char *verp = version_of(path);
    if (verp != 0)
      *verp = EOS;
    return path;
}
#endif

/*
 * Check if the given path appears in the path-list
 */
int
find_in_path_list(const char *path_list, char *path)
{
    char temp[NFILEN];
    int found = FALSE;
    char find[NFILEN];

    vl_strncpy(find, SL_TO_BSL(path), sizeof(find));
    TRACE(("find_in_path_list\n\t%s\n\t%s\n", TRACE_NULL(path_list), find));
    while ((path_list = parse_pathlist(path_list, temp)) != 0) {
#if OPT_CASELESS
      if (!stricmp(temp, find))
#else
      if (!strcmp(temp, find))
#endif
      {
          found = TRUE;
          break;
      }
    }
    TRACE(("\t-> %d\n", found));
    return found;
}

#if 0
/*
 * Prepend the given path to a path-list
 */
void
prepend_to_path_list(char **path_list, char *path)
{
    char *s, *t;
    size_t need;
    char find[NFILEN];
    struct stat sb;

    vl_strncpy(find, SL_TO_BSL(path), sizeof(find));
    if (*path_list == 0 || **path_list == 0) {
      append_to_path_list(path_list, find);
    } else if (!find_in_path_list(*path_list, find)
#if SYS_UNIX
             && file_stat(find, &sb) == 0
#endif
      ) {
      need = strlen(find) + 2;
      if ((t = *path_list) != 0) {
          need += strlen(t);
      } else {
          t = "";
      }
      if ((s = typeallocn(char, need)) != 0) {
          lsprintf(s, "%s%c%s", find, vl_pathchr, t);
          if (*path_list != 0)
            free(*path_list);
          *path_list = s;
      }
    }
    TRACE(("prepend_to_path_list\n\t%s\n\t%s\n", *path_list, find));
}
#endif

/*
 * Append the given path to a path-list
 */
void
append_to_path_list(char **path_list, char *path)
{
    char *s, *t;
    size_t need;
    char find[NFILEN];
#if SYS_UNIX
    struct stat sb;
#endif

    vl_strncpy(find, SL_TO_BSL(path), sizeof(find));
    if (!find_in_path_list(*path_list, find)
#if SYS_UNIX
      && file_stat(find, &sb) == 0
#endif
      ) {
      need = strlen(find) + 2;
      if ((t = *path_list) != 0) {
          need += strlen(t);
      } else {
          t = "";
      }
      if ((s = typeallocn(char, need)) != 0) {
          if (*t != EOS)
            lsprintf(s, "%s%c%s", t, vl_pathchr, find);
          else
            strcpy(s, find);
          if (*path_list != 0)
            free(*path_list);
          *path_list = s;
      }
    }
    TRACE(("append_to_path_list\n\t%s\n\t%s\n", *path_list, find));
}

/*
 * FUNCTION
 *   path_trunc(char *path,
 *              int  max_path_len,
 *              char *trunc_buf,
 *              int  trunc_buf_len)
 *
 *   path          - an arbitrarily long file path
 *
 *   max_path_len  - maximum acceptable output path length.  Must be >= 5.
 *
 *   trunc_buf     - format the possibly truncated path in this buffer.
 *
 *   trunc_buf_len - sizeof(trunc_buf).  Must be >= 5.
 *
 * DESCRIPTION
 *   Shorten a path from _the left_ to fit within the bounds of 
 *   both max_output_len and trunc_buf_len.  This function is useful when
 *   displaying path names in the message line.
 *
 * RETURNS
 *   trunc_buf
 */

char *
path_trunc(char *path, int max_path_len, char *trunc_buf, int trunc_buf_len)
{
    int max_len, path_len;

    if (trunc_buf_len < 5 || max_path_len < 5)
      return (path);          /* nuh-uh */

    max_len = max_path_len;
    if (trunc_buf_len - 1 < max_len)      /* "- 1" -> terminating NUL */
      max_len = trunc_buf_len - 1;
    path_len = strlen(path);
    if (path_len <= max_len)
      strcpy(trunc_buf, path);      /* fits -- no issues */
    else {
#if SYS_VMS
      strcpy(trunc_buf, ">>>");
      max_len -= sizeof(">>>") - 1;
#else
      strcpy(trunc_buf, "...");
      max_len -= sizeof("...") - 1;
#endif
      strcat(trunc_buf, path + path_len - max_len);
    }
    return (trunc_buf);
}

#ifdef HAVE_PUTENV
/*
 * Put the libdir in our path so we do not have to install the filters in the
 * regular $PATH.  If we can do this right after forking, it will not affect
 * the path for subshells invoked via ":sh".
 */
void
append_libdir_to_path(void)
{
    char *env, *tmp;
    const char *cp;
    char buf[NFILEN];

    if (libdir_path != 0
      && (tmp = getenv("PATH")) != 0) {
      env = strmalloc(tmp);
      cp = libdir_path;
      while ((cp = parse_pathlist(cp, buf)) != 0) {
          append_to_path_list(&env, buf);
      }
      if (strcmp(tmp, env)) {
          tmp = (char *) malloc(6 + strlen(env));
          lsprintf(tmp, "PATH=%s", env);
          putenv(tmp);
          TRACE(("putenv %s\n", tmp));
      } else {
          free(env);
      }
    }
}
#else
void
append_libdir_to_path(void)
{
}
#endif

#if NO_LEAKS
void
path_leaks(void)
{
#if SYS_UNIX
    while (user_paths != NULL) {
      register UPATH *paths = user_paths;
      user_paths = paths->next;
      free(paths->name);
      free(paths->path);
      free((char *) paths);
    }
#endif
}
#endif /* NO_LEAKS */

Generated by  Doxygen 1.6.0   Back to index