home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / vile-src.zip / vile-8.1 / path.c < prev    next >
C/C++ Source or Header  |  1998-05-13  |  41KB  |  1,922 lines

  1. /*    PATH.C
  2.  *        The routines in this file handle the conversion of pathname
  3.  *        strings.
  4.  *
  5.  * $Header: /usr/build/vile/vile/RCS/path.c,v 1.84 1998/05/14 00:18:13 tom Exp $
  6.  *
  7.  *
  8.  */
  9.  
  10. #ifdef _WIN32
  11. # include <windows.h>
  12. #endif
  13.  
  14. #include    "estruct.h"
  15. #include        "edef.h"
  16.  
  17. #if SYS_UNIX
  18. #include <sys/types.h>
  19. #include <pwd.h>
  20. #endif
  21.  
  22. #if SYS_VMS
  23. #include <starlet.h>
  24. #include <file.h>
  25. #endif
  26.  
  27. #if SYS_OS2
  28. # define INCL_DOSFILEMGR
  29. # define INCL_ERRORS
  30. # include <os2.h>
  31. #endif
  32.  
  33. #include "dirstuff.h"
  34.  
  35. #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
  36. # define S_ISDIR(m)  (((m) & S_IFMT) == S_IFDIR)
  37. #endif
  38.  
  39. #if (SYS_WIN31 && CC_TURBO) || SYS_WINNT
  40. # include <direct.h>
  41. # define curdrive() (_getdrive() + ('A' - 1))
  42. # define curr_dir_on_drive(d) _getdcwd(toUpper(d) - ('A' - 1), temp, sizeof(temp))
  43. #endif
  44.  
  45. #if SYS_OS2_EMX
  46. # define curdrive() _getdrive()
  47. static char *
  48. curr_dir_on_drive(int d)
  49. {
  50.     static char buffer[NFILEN];
  51.     char *s;
  52.     if (_getcwd1(buffer, d) < 0)
  53.         return 0;
  54.     /* EMX 0.9b documents _getcwd1 fixes slashes, but it doesn't work */
  55.     for (s = buffer; *s; s++)
  56.         if (*s == '\\') *s = '/';
  57.     return buffer;
  58. }
  59. #endif
  60.  
  61. #ifdef GMDRESOLVE_LINKS
  62. #if HAVE_SYS_ITIMER_H && SYSTEM_LOOKS_LIKE_SCO
  63. #include <sys/itimer.h>
  64. #endif
  65. static    char * resolve_directory ( char *path_name, char **file_namep );
  66. #endif
  67.  
  68. static    char * canonpath ( char *ss );
  69. static    int is_absolute_pathname( char *path );
  70. static    int is_relative_pathname( const char *path );
  71.  
  72. #if OPT_CASELESS
  73. static    int case_correct_path ( char *old_file, char *new_file );
  74. #endif
  75.  
  76. /*
  77.  * Fake directory-routines for system where we cannot otherwise figure out how
  78.  * to read the directory-file.
  79.  */
  80. #if USE_LS_FOR_DIRS
  81. DIR *
  82. opendir (char *path)
  83. {
  84.     static    const char fmt[] = "/bin/ls %s";
  85.     char    lscmd[NFILEN+sizeof(fmt)];
  86.  
  87.     (void)lsprintf(lscmd, fmt, path);
  88.     return npopen(lscmd, "r");
  89. }
  90.  
  91. DIRENT *
  92. readdir (DIR *dp)
  93. {
  94.     static    DIRENT    dummy;
  95.  
  96.     if ((fgets(dummy.d_name, NFILEN, dp)) != NULL) {
  97.         /* zap the newline */
  98.         dummy.d_name[strlen(dummy.d_name)-1] = EOS;
  99.         return &dummy;
  100.     }
  101.     return 0;
  102. }
  103.  
  104. int
  105. closedir (DIR *dp)
  106. {
  107.     (void)npclose(dp);
  108.     return 0;
  109. }
  110. #endif
  111.  
  112. /*
  113.  * Use this routine to fake compatibility with unix directory routines.
  114.  */
  115. #if OLD_STYLE_DIRS
  116. DIRENT *
  117. readdir(DIR *dp)
  118. {
  119.     static    DIRENT    dbfr;
  120.     return (fread(&dbfr, sizeof(dbfr), 1, dp)
  121.                 ? &dbfr
  122.                 : (DIRENT *)0);
  123. }
  124. #endif
  125.  
  126. #if OPT_MSDOS_PATH
  127. /*
  128.  * If the pathname begins with an MSDOS-drive, return the pointer past it.
  129.  * Otherwise, return null.
  130.  */
  131. char *
  132. is_msdos_drive(char *path)
  133. {
  134. #if OPT_UNC_PATH
  135.     if (is_slashc(path[0]) && is_slashc(path[1]))
  136.         return (path+1);
  137. #endif
  138.     if (isAlpha(path[0]) && path[1] == ':')
  139.         return (path+2);
  140.     return 0;
  141. }
  142. #endif
  143.  
  144. #if OPT_VMS_PATH
  145. #define VMSPATH_END_NODE   1
  146. #define VMSPATH_END_DEV    2
  147. #define VMSPATH_BEGIN_DIR  3
  148. #define VMSPATH_NEXT_DIR   4
  149. #define VMSPATH_END_DIR    5
  150. #define    VMSPATH_BEGIN_FILE 6
  151. #define VMSPATH_BEGIN_TYP  7
  152. #define VMSPATH_BEGIN_VER  8
  153.  
  154. /*
  155.  * Returns true if the string is delimited in a manner compatible with VMS
  156.  * pathnames.  To be consistent with the use of 'is_pathname()', insist that
  157.  * at least the "[]" characters be given.
  158.  *
  159.  * Complete syntax:
  160.  *    node::device:[dir1.dir2]filename.type;version
  161.  *        ^1     ^2^3   ^4  ^5^6      ^7   ^8
  162.  */
  163. int
  164. is_vms_pathname(
  165. const char *path,
  166. int    option)        /* true:directory, false:file, -true:don't care */
  167. {
  168.     const char *base = path;
  169.     int    this    = 0,
  170.         next    = -1;
  171.  
  172.     if (*path == EOS)    /* this can happen with null buffer-name */
  173.         return FALSE;
  174.  
  175.     while (ispath(*path)) {
  176.         switch (*path) {
  177.         case '[':
  178.             if (this >= VMSPATH_BEGIN_FILE)
  179.                 return FALSE;
  180.             next = VMSPATH_BEGIN_DIR;
  181.             break;
  182.         case ']':
  183.             if (this < VMSPATH_BEGIN_DIR)
  184.                 return FALSE;
  185.             if (path != base    /* rooted logical? */
  186.              && path[1] == '['
  187.              && path[-1] == '.')
  188.                 path++;
  189.             else
  190.                 next = VMSPATH_END_DIR;
  191.             break;
  192.         case '.':
  193.             if (this >= VMSPATH_BEGIN_TYP)
  194.                 return FALSE;
  195.             next = (this >= VMSPATH_END_DIR)
  196.                 ? VMSPATH_BEGIN_TYP
  197.                 : (this >= VMSPATH_BEGIN_DIR
  198.                     ? VMSPATH_NEXT_DIR
  199.                     : VMSPATH_BEGIN_TYP);
  200.             break;
  201.         case ';':
  202.             next = VMSPATH_BEGIN_VER;
  203.             break;
  204.         case ':':
  205.             if (path[1] == ':') {
  206.                 path++;    /* eat "::" */
  207.                 if (this >= VMSPATH_END_NODE)
  208.                     return FALSE;
  209.                 next = VMSPATH_END_NODE;
  210.             } else
  211.                 next = VMSPATH_END_DEV;
  212.             break;
  213.         case '!':
  214.         case '/':
  215.         case '~':
  216.             return FALSE;    /* a DEC-shell name */
  217.         default:
  218.             if (!ispath(*path))
  219.                 return FALSE;
  220.             next = (this == VMSPATH_END_DIR)
  221.                 ? VMSPATH_BEGIN_FILE
  222.                 : this;
  223.             break;
  224.         }
  225.         if (next < this)
  226.             break;
  227.         this = next;
  228.         path++;
  229.     }
  230.  
  231.     if ((*path != EOS)
  232.      || (this  <  next))
  233.         return FALSE;
  234.  
  235.     if (this == 0)
  236.         this = VMSPATH_BEGIN_FILE;
  237.  
  238.     return (option == TRUE  && (this == VMSPATH_END_DIR))    /* dir? */
  239.       ||   (option == TRUE  && (this == VMSPATH_END_DEV))    /* dev? */
  240.       ||   (option == FALSE && (this >= VMSPATH_BEGIN_FILE))/* file? */
  241.       ||   (option == -TRUE && (this >= VMSPATH_END_DIR    /* anything? */
  242.                  || this <  VMSPATH_BEGIN_DIR));
  243. }
  244. #endif
  245.  
  246. #if OPT_VMS_PATH
  247. /*
  248.  * Returns a pointer to the argument's last path-leaf (i.e., filename).
  249.  */
  250. char *
  251. vms_pathleaf(char *path)
  252. {
  253.     register char    *s;
  254.     for (s = skip_string(path);
  255.         s > path && !strchr(":]", s[-1]);
  256.             s--)
  257.         ;
  258.     return s;
  259. }
  260. #endif
  261.  
  262. /*
  263.  * Returns a pointer to the argument's last path-leaf (i.e., filename).
  264.  */
  265.  
  266. #if !OPT_VMS_PATH
  267. #define    unix_pathleaf    pathleaf
  268. #endif
  269.  
  270. char *
  271. unix_pathleaf(char *path)
  272. {
  273.     register char *s = last_slash(path);
  274.     if (s == 0) {
  275. #if OPT_MSDOS_PATH
  276.         if (!(s = is_msdos_drive(path)))
  277. #endif
  278.         s = path;
  279.     } else
  280.         s++;
  281.     return s;
  282. }
  283.  
  284. #if OPT_VMS_PATH
  285. char *
  286. pathleaf(char *path)
  287. {
  288.     if (is_vms_pathname(path, -TRUE))
  289.         return vms_pathleaf(path);
  290.     return unix_pathleaf(path);
  291. }
  292. #endif
  293.  
  294. /*
  295.  * Concatenates a directory and leaf name to form a full pathname
  296.  */
  297. char *
  298. pathcat (char *dst, const char *path, char *leaf)
  299. {
  300.     char    save_path[NFILEN];
  301.     char    save_leaf[NFILEN];
  302.     register char    *s = dst;
  303.  
  304.     if (path == 0
  305.      || *path == EOS
  306.      || is_absolute_pathname(leaf))
  307.         return strcpy(dst, leaf);
  308.  
  309.     path = strcpy(save_path, path);        /* in case path is in dst */
  310.     leaf = strcpy(save_leaf, leaf);        /* in case leaf is in dst */
  311.  
  312.     (void)strcpy(s, path);
  313.     s += strlen(s) - 1;
  314.  
  315. #if OPT_VMS_PATH
  316.     if (!is_vms_pathname(dst, TRUE))    /* could be DecShell */
  317. #endif
  318.     if (!is_slashc(*s)) {
  319.         *(++s) = SLASHC;
  320.     }
  321.  
  322.     (void)strcpy(s+1, leaf);
  323.  
  324. #if OPT_VMS_PATH
  325.     if (is_vms_pathname(path, -TRUE)
  326.      && is_vms_pathname(leaf, -TRUE)
  327.      && !is_vms_pathname(dst, -TRUE))
  328.         (void)strcpy(dst, leaf);
  329. #endif
  330.     return dst;
  331. }
  332.  
  333. /*
  334.  * Tests to see if the string contains a slash-delimiter.  If so, return the
  335.  * last one (so we can locate the path-leaf).
  336.  */
  337. char *
  338. last_slash(char *fn)
  339. {
  340.     register char *s;
  341.  
  342.     if (*fn != EOS)
  343.         for (s = skip_string(fn); s > fn; s--)
  344.             if (is_slashc(s[-1]))
  345.                 return s - 1;
  346.     return 0;
  347. }
  348.  
  349. /*
  350.  * If a pathname begins with "~", lookup the name in the password-file.  Cache
  351.  * the names that we lookup, because searching the password-file can be slow,
  352.  * and users really don't move that often.
  353.  */
  354. #if SYS_UNIX
  355. typedef    struct    _upath {
  356.     struct    _upath *next;
  357.     char    *name;
  358.     char    *path;
  359.     } UPATH;
  360.  
  361. static    UPATH    *user_paths;
  362.  
  363. static char *
  364. save_user(const char *name, const char *path)
  365. {
  366.     register UPATH *q;
  367.  
  368.     if (name != NULL
  369.      && path != NULL
  370.      && (q = typealloc(UPATH)) != NULL) {
  371.         if ((q->name = strmalloc(name)) != NULL
  372.          && (q->path = strmalloc(path)) != NULL) {
  373.             q->next = user_paths;
  374.             user_paths = q;
  375.             return q->path;
  376.         } else {
  377.             FreeIfNeeded(q->name);
  378.             FreeIfNeeded(q->path);
  379.             free((char *)q);
  380.         }
  381.     }
  382.     return NULL;
  383. }
  384.  
  385. static char *
  386. find_user(const char *name)
  387. {
  388.     register struct    passwd *p;
  389.     register UPATH    *q;
  390.  
  391.     if (name != NULL) {
  392.         for (q = user_paths; q != NULL; q = q->next) {
  393.             if (!strcmp(q->name, name)) {
  394.                 return q->path;
  395.             }
  396.         }
  397.  
  398.         /* not-found, do a lookup */
  399.         if (*name != EOS)
  400.             p = getpwnam(name);
  401.         else {
  402.             p = getpwuid((int)getuid());
  403.             if (p == 0) {
  404.                 char *env = getenv("HOME");
  405.                 if (env != 0)
  406.                     return save_user(name, env);
  407.             }
  408.         }
  409.  
  410.         if (p != NULL)
  411.             return save_user(name, p->pw_dir);
  412. #if NEEDED
  413.     } else {    /* lookup all users (for globbing) */
  414.         (void)setpwent();
  415.         while ((p = getpwent()) != NULL)
  416.             (void)save_user(p->pw_name, p->pw_dir);
  417.         (void)endpwent();
  418. #endif
  419.     }
  420.     return NULL;
  421. }
  422.  
  423. char *
  424. home_path(char *path)
  425. {
  426.     if (*path == '~') {
  427.         char    temp[NFILEN];
  428.         char *s;
  429.         char *d;
  430.  
  431.         /* parse out the user-name portion */
  432.         for (s = path+1, d = temp; (*d = *s) != EOS; d++, s++) {
  433.             if (is_slashc(*d)) {
  434.                 *d = EOS;
  435.                 s++;
  436.                 break;
  437.             }
  438.         }
  439.  
  440. #if OPT_VMS_PATH
  441.         (void)mklower(temp);
  442. #endif
  443.         if ((d = find_user(temp)) != NULL)
  444.             (void)pathcat(path, d, s);
  445.     }
  446.     return path;
  447. }
  448. #endif
  449.  
  450. #ifdef GMDRESOLVE_LINKS
  451. /*
  452.  * Some of this code was "borrowed" from the GNU C library (getcwd.c).  It has
  453.  * been largely rewritten and bears little resemblance to what it started out
  454.  * as.
  455.  *
  456.  * The purpose of this code is to generalize getcwd.  The idea is to pass it as
  457.  * input some path name.  This pathname can be relative, absolute, whatever.
  458.  * It may have elements which reference symbolic links.  The output from this
  459.  * function will be the absolute pathname representing the same file.
  460.  * Actually, it only returns the directory.  If the thing you pass it is a
  461.  * directory, you'll get that directory back (canonicalized).  If you pass it a
  462.  * path to an ordinary file, you'll get back the canonicalized directory which
  463.  * contains that file.
  464.  *
  465.  * The way that this code works is similar to the classic implementation of
  466.  * getcwd (or getwd).  The difference is that once it finds a directory, it
  467.  * will cache it.  If that directory is referenced again, finding it will be
  468.  * very fast.  The callee of this function should not free up the pointer which
  469.  * is returned.  This will be done automatically by the caching code.  The
  470.  * value returned will exist at least up until the next call It should not be
  471.  * relied on any longer than this.  Care should be taken not to corrupt the
  472.  * value returned.
  473.  *
  474.  * FIXME: there should be some way to reset the cache in case directories are
  475.  * renamed.
  476.  */
  477.  
  478. #define CPN_CACHE_SIZE 64
  479. #define CPN_CACHE_MASK (CPN_CACHE_SIZE-1)
  480.  
  481. #if !defined(HAVE_SETITIMER) || !defined(HAVE_SIGACTION)
  482. #define TimedStat stat
  483. #else /* !defined(HAVE_SETITIMER) */
  484.  
  485. #define TimedStat stat_with_timeout
  486.  
  487. static    jmp_buf stat_jmp_buf;        /* for setjmp/longjmp on timeout */
  488.  
  489. static SIGT
  490. StatHandler(int ACTUAL_SIG_ARGS)
  491. {
  492.     longjmp(stat_jmp_buf, signo);
  493.     SIGRET;
  494. }
  495.  
  496. static int
  497. stat_with_timeout(
  498.     const char *path,
  499.     struct stat *statbuf)
  500. {
  501.     struct sigaction newact;
  502.     struct sigaction oldact;
  503.     sigset_t newset;
  504.     sigset_t oldset;
  505.     struct itimerval timeout;
  506.     struct itimerval oldtimerval;
  507.     int retval, stat_errno;
  508.  
  509.     newact.sa_handler = StatHandler;
  510.     newact.sa_flags = 0;
  511.     if (sigemptyset(&newact.sa_mask) < 0
  512.      || sigfillset(&newset) < 0
  513.      || sigdelset(&newset, SIGPROF) < 0
  514.      || sigprocmask(SIG_BLOCK, &newset, &oldset) < 0)
  515.         return -1;
  516.  
  517.     if (sigaction(SIGPROF, &newact, &oldact) < 0) {
  518.         sigprocmask(SIG_SETMASK, &oldset, (sigset_t *)0);
  519.         return -1;
  520.     }
  521.  
  522.     timeout.it_interval.tv_sec  = 0;
  523.     timeout.it_interval.tv_usec = 0;
  524.     timeout.it_value.tv_sec     = 0;
  525.     timeout.it_value.tv_usec    = 75000;
  526.  
  527.     (void)setitimer(ITIMER_PROF, &timeout, &oldtimerval);
  528.  
  529.     /*
  530.      * POSIX says that 'stat()' won't return an error if it's interrupted,
  531.      * so we force an error by making a longjmp from the timeout handler,
  532.      * and forcing the error return status.
  533.      */
  534.     if (setjmp(stat_jmp_buf)) {
  535.         retval = -1;
  536.         stat_errno = EINTR;
  537.     } else {
  538.         retval = stat(path, statbuf);
  539.         stat_errno = errno;
  540.     }
  541.  
  542.     timeout.it_value.tv_usec = 0;
  543.     (void)setitimer(ITIMER_PROF, &timeout, (struct itimerval *)0);
  544.  
  545.     (void)sigaction(SIGPROF, &oldact, (struct sigaction *)0);
  546.     (void)sigprocmask(SIG_SETMASK, &oldset, (sigset_t *)0);
  547.     (void)setitimer(ITIMER_PROF, &oldtimerval, (struct itimerval *)0);
  548.  
  549.     errno = stat_errno;
  550.     return retval;
  551. }
  552. #endif /* !defined(HAVE_SETITIMER) */
  553.  
  554. static char *
  555. resolve_directory(
  556.     char *path_name,
  557.     char **file_namep)
  558. {
  559.     dev_t rootdev, thisdev;
  560.     ino_t rootino, thisino;
  561.     struct stat  st;
  562.  
  563.     static const char rootdir[] = { SLASHC, EOS };
  564.  
  565.     static TBUFF *last_leaf;
  566.     static TBUFF *last_path;
  567.     static TBUFF *last_temp;
  568.  
  569.     char         *temp_name;
  570.     char         *tnp;    /* temp name pointer */
  571.     char         *temp_path; /* the path that we've determined */
  572.  
  573.     ALLOC_T       len;    /* temporary for length computations */
  574.  
  575.     static struct cpn_cache {
  576.         dev_t ce_dev;
  577.         ino_t ce_ino;
  578.         TBUFF *ce_dirname;
  579.     } cache_entries[CPN_CACHE_SIZE];
  580.  
  581.     struct cpn_cache *cachep;
  582.  
  583.     tb_free(&last_leaf);
  584.     *file_namep = NULL;
  585.     len = strlen(path_name);
  586.  
  587.     if (!tb_alloc(&last_temp, len + 1))
  588.         return NULL;
  589.     tnp = (temp_name = tb_values(last_temp)) + len;
  590.  
  591.     if (!tb_alloc(&last_path, len + 1))
  592.         return NULL;
  593.     *(temp_path = tb_values(last_path)) = EOS;
  594.  
  595.     (void)strcpy(temp_name, path_name);
  596.  
  597.     /*
  598.      * Test if the given pathname is an actual directory, or not.  If it's
  599.      * a symbolic link, we'll have to determine if it points to a directory
  600.      * before deciding how to split it.
  601.      */
  602.     if (lstat(temp_name, &st) < 0)
  603.         st.st_mode = S_IFREG;    /* assume we're making a file... */
  604.  
  605.     if (!S_ISDIR(st.st_mode)) {
  606.         int levels = 0;
  607.         char target[NFILEN];
  608.  
  609.         /* loop until no more links */
  610.         while ((st.st_mode & S_IFMT) == S_IFLNK) {
  611.             int got = 0;
  612.  
  613.             if (levels++ > 4    /* FIXME */
  614.              || (got = readlink(temp_name,
  615.                      target, sizeof(target)-1)) < 0) {
  616.                 return NULL;
  617.             }
  618.             target[got] = EOS;
  619.  
  620.             if (tb_alloc(&last_temp, (ALLOC_T)(strlen(temp_name)+got+1)) == 0)
  621.                 return NULL;
  622.  
  623.             temp_name = tb_values(last_temp);
  624.  
  625.             if (!is_slashc(target[0])) {
  626.                 tnp = pathleaf(temp_name);
  627.                 if (tnp != temp_name && !is_slashc(tnp[-1]))
  628.                     *tnp++ = SLASHC;
  629.                 (void)strcpy(tnp, target);
  630.             } else {
  631.                 (void)strcpy(temp_name, target);
  632.             }
  633.             if (lstat(temp_name, &st) < 0)
  634.                 break;
  635.         }
  636.  
  637.         /*
  638.          * If we didn't resolve any symbolic links, we can find the
  639.          * filename leaf in the original 'path_name' argument.
  640.          */
  641.         tnp = last_slash(temp_name);
  642.         if (tnp == NULL) {
  643.             tnp = temp_name;
  644.             if (tb_scopy(&last_leaf, tnp) == 0)
  645.                 return NULL;
  646.             *tnp++ = '.';
  647.             *tnp = EOS;
  648.         } else if (tb_scopy(&last_leaf, tnp + 1) == 0) {
  649.             return NULL;
  650.         }
  651.         if (tnp == temp_name && is_slashc(*tnp)) /* initial slash */
  652.             tnp++;
  653.         *tnp = EOS;
  654.  
  655.         /*
  656.          * If the parent of the given path_name isn't a directory, give
  657.          * up...
  658.          */
  659.         if (TimedStat(temp_name, &st) < 0 || !S_ISDIR(st.st_mode))
  660.             return NULL;
  661.     }
  662.  
  663.     /*
  664.      * Now, 'temp_name[]' contains a null-terminated directory-path, and
  665.      * 'tnp' points to the null.  If we've set file_namep, we've allocated
  666.      * a pointer since it may be pointing within the temp_name string --
  667.      * which may be overwritten.
  668.      */
  669.     *file_namep = tb_values(last_leaf);
  670.  
  671.     thisdev = st.st_dev;
  672.     thisino = st.st_ino;
  673.  
  674.     cachep =  &cache_entries[(thisdev ^ thisino) & CPN_CACHE_MASK];
  675.     if (tb_values(cachep->ce_dirname) != 0
  676.      && cachep->ce_ino == thisino
  677.      && cachep->ce_dev == thisdev) {
  678.         return tb_values(cachep->ce_dirname);
  679.     } else {
  680.         cachep->ce_ino = thisino;
  681.         cachep->ce_dev = thisdev;
  682.         tb_free(&(cachep->ce_dirname));    /* will reset iff ok */
  683.     }
  684.  
  685.     if (TimedStat(rootdir, &st) < 0)
  686.         return NULL;
  687.  
  688.     rootdev = st.st_dev;
  689.     rootino = st.st_ino;
  690.  
  691.     while ((thisdev != rootdev)
  692.        ||  (thisino != rootino)) {
  693.         register DIR *dp;
  694.         register DIRENT *de;
  695.         dev_t dotdev;
  696.         ino_t dotino;
  697.         char  mount_point;
  698.         SIZE_T namelen = 0;
  699.  
  700.         len = tnp - temp_name;
  701.         if (tb_alloc(&last_temp, 4 + len) == 0)
  702.             return NULL;
  703.  
  704.         tnp = (temp_name = tb_values(last_temp)) + len;
  705.         *tnp++ = SLASHC;
  706.         *tnp++ = '.';
  707.         *tnp++ = '.';
  708.         *tnp   = EOS;
  709.  
  710.         /* Figure out if this directory is a mount point.  */
  711.         if (TimedStat(temp_name, &st) < 0)
  712.             return NULL;
  713.  
  714.         dotdev = st.st_dev;
  715.         dotino = st.st_ino;
  716.         mount_point = (dotdev != thisdev);
  717.  
  718.         /* Search for the last directory.  */
  719.         if ((dp = opendir(temp_name)) != 0) {
  720.             int    found = FALSE;
  721.  
  722.             while ((de = readdir(dp)) != NULL) {
  723. #if USE_D_NAMLEN
  724.                 namelen = de->d_namlen;
  725. #else
  726.                 namelen = strlen(de->d_name);
  727. #endif
  728.                 /* Ignore "." and ".." */
  729.                 if (de->d_name[0] == '.'
  730.                  && (namelen == 1
  731.                   || (namelen == 2 && de->d_name[1] == '.')))
  732.                     continue;
  733.  
  734.                 if (mount_point || (de->d_ino == thisino)) {
  735.                     len = tnp - temp_name;
  736.                     if (tb_alloc(&last_temp, len + namelen + 1) == 0)
  737.                         break;
  738.  
  739.                     temp_name = tb_values(last_temp);
  740.                     tnp = temp_name + len;
  741.  
  742.                     *tnp = SLASHC;
  743.                     (void)strncpy(tnp+1, de->d_name, namelen);
  744.                     tnp[namelen+1] = EOS;
  745.  
  746.                     if (TimedStat(temp_name, &st) == 0
  747.                      && st.st_dev == thisdev
  748.                      && st.st_ino == thisino) {
  749.                         found = TRUE;
  750.                         break;
  751.                     }
  752.                 }
  753.             }
  754.  
  755.             if (found) {
  756.                 /*
  757.                  * Push the latest directory-leaf before the
  758.                  * string already in 'temp_path[]'.
  759.                  */
  760.                 len = strlen(temp_path) + 1;
  761.                 if (tb_alloc(&last_path, len + namelen + 1) == 0) {
  762.                     (void) closedir(dp);
  763.                     return NULL;
  764.                 }
  765.                 temp_path = tb_values(last_path);
  766.                 while (len-- != 0)
  767.                     temp_path[namelen+1+len] = temp_path[len];
  768.                 temp_path[0] = SLASHC;
  769.                 (void)memcpy(temp_path+1, de->d_name, namelen);
  770.             }
  771.             (void) closedir(dp);
  772.             if (!found)
  773.                 return NULL;
  774.         }
  775.         else    /* could't open directory */
  776.             return NULL;
  777.  
  778.         thisdev = dotdev;
  779.         thisino = dotino;
  780.     }
  781.  
  782.     if (tb_scopy(&(cachep->ce_dirname),
  783.         *temp_path ? temp_path : rootdir) == 0)
  784.         return NULL;
  785.  
  786.     return tb_values(cachep->ce_dirname);
  787. }
  788. #endif    /* defined(GMDRESOLVE_LINKS) */
  789.  
  790. #if OPT_CASELESS
  791.  
  792. /*
  793.  * The function case_correct_path is intended to determine the true
  794.  * case of all pathname components of a syntactically canonicalized
  795.  * pathname for operating systems on which OPT_CASELESS applies.
  796.  */
  797.  
  798. #if SYS_WINNT
  799.  
  800. static int
  801. case_correct_path(char *old_file, char *new_file)
  802. {
  803.     WIN32_FIND_DATA fd;
  804.     HANDLE h;
  805.     int len;
  806.     char *next, *current, *end, *sofar;
  807.     char tmp_file[MAX_PATH];
  808.  
  809.     /* Handle old_file == new_file safely. */
  810.     (void)strcpy(tmp_file, old_file);
  811.     old_file = tmp_file;
  812.  
  813.     if (is_slashc(old_file[0]) && is_slashc(old_file[1])) {
  814.  
  815.         /* Handle UNC filenames. */
  816.         current = old_file + 2;
  817.         next = strchr(current, SLASHC);
  818.         if (next)
  819.             next = strchr(next + 1, SLASHC);
  820.  
  821.         /* Canonicalize the system name and share name. */
  822.         if (next)
  823.             len = next - old_file + 1;
  824.         else
  825.             len = strlen(old_file);
  826.         (void)memcpy(new_file, old_file, len);
  827.         new_file[len] = EOS;
  828.         (void)mklower(new_file);
  829.         if (!next)
  830.             return 0;
  831.         sofar = new_file + len;
  832.         current = next + 1;
  833.     }
  834.     else {
  835.  
  836.         /* Canonicalize a leading drive letter, if any. */
  837.         if (old_file[0] && old_file[1] == ':') {
  838.             new_file[0] = old_file[0];
  839.             new_file[1] = old_file[1];
  840.             if (isLower(new_file[0]))
  841.                 new_file[0] = toUpper(new_file[0]);
  842.             current = old_file + 2;
  843.             sofar = new_file + 2;
  844.         }
  845.         else {
  846.             current = old_file;
  847.             sofar = new_file;
  848.         }
  849.  
  850.         /* Skip a leading slash, if any. */
  851.         if (is_slashc(*current)) {
  852.             current++;
  853.             *sofar++ = SLASHC;
  854.         }
  855.     }
  856.  
  857.     /* Canonicalize each pathname prefix. */
  858.     end = skip_string(old_file);
  859.     while (current < end) {
  860.         next = strchr(current, SLASHC);
  861.         if (!next)
  862.             next = end;
  863.         len = next - current;
  864.         (void)memcpy(sofar, current, len);
  865.         sofar[len] = EOS;
  866.         h = FindFirstFile(new_file, &fd);
  867.         if (h != INVALID_HANDLE_VALUE) {
  868.             FindClose(h);
  869.             (void)strcpy(sofar, fd.cFileName);
  870.             sofar += strlen(sofar);
  871.         }
  872.         else
  873.             sofar += len;
  874.         if (next != end)
  875.             *sofar++ = SLASHC;
  876.         current = next + 1;
  877.     }
  878.     return 0;
  879. }
  880.  
  881. #else /* !SYS_WINNT */
  882.  
  883. #if SYS_OS2
  884.  
  885. int
  886. is_case_preserving(const char *name)
  887. {
  888.     int case_preserving = 1;
  889.  
  890.     /* Determine if the filesystem is case-preserving. */
  891.     if (name[0] && name[1] == ':') {
  892.         char drive_name[3];
  893.         char buffer[sizeof(FSQBUFFER2) + 3*CCHMAXPATH];
  894.         FSQBUFFER2 *pbuffer = (FSQBUFFER2 *) buffer;
  895.         ULONG len = sizeof(buffer);
  896.         APIRET rc;
  897.  
  898.         drive_name[0] = name[0];
  899.         drive_name[1] = name[1];
  900.         drive_name[2] = EOS;
  901.         rc = DosQueryFSAttach(drive_name, 0, FSAIL_QUERYNAME,
  902.             pbuffer, &len);
  903.         if (rc == NO_ERROR) {
  904.             char *name = pbuffer->szName + pbuffer->cbName + 1;
  905.  
  906.             if (strcmp(name, "FAT") == 0)
  907.                 case_preserving = 0;
  908.         }
  909.     }
  910.     return case_preserving;
  911. }
  912.  
  913. static int
  914. case_correct_path(char *old_file, char *new_file)
  915. {
  916.     FILEFINDBUF3 fb;
  917.     ULONG entries;
  918.     HDIR hdir;
  919.     APIRET rc;
  920.     char *next, *current, *end, *sofar;
  921.     char tmp_file[NFILEN + 2];
  922.     ULONG len;
  923.     int case_preserving = is_case_preserving(old_file);
  924.  
  925.     /* Handle old_file == new_file safely. */
  926.     (void)strcpy(tmp_file, old_file);
  927.     old_file = tmp_file;
  928.  
  929.     /* If it isn't case-preserving then just down-case it. */
  930.     if (!case_preserving) {
  931.         (void) mklower(strcpy(new_file, old_file));
  932.         return 0;
  933.     }
  934.  
  935.     /* Canonicalize a leading drive letter, if any. */
  936.     if (old_file[0] && old_file[1] == ':') {
  937.         new_file[0] = old_file[0];
  938.         new_file[1] = old_file[1];
  939.         if (isLower(new_file[0]))
  940.             new_file[0] = toUpper(new_file[0]);
  941.         current = old_file + 2;
  942.         sofar = new_file + 2;
  943.     }
  944.     else {
  945.         current = old_file;
  946.         sofar = new_file;
  947.     }
  948.  
  949.     /* Skip a leading slash, if any. */
  950.     if (is_slashc(*current)) {
  951.         current++;
  952.         *sofar++ = SLASHC;
  953.     }
  954.  
  955.     /* Canonicalize each pathname prefix. */
  956.     end = skip_string(old_file);
  957.     while (current < end) {
  958.         next = strchr(current, SLASHC);
  959.         if (!next)
  960.             next = end;
  961.         len = next - current;
  962.         (void)memcpy(sofar, current, len);
  963.         sofar[len] = EOS;
  964.         hdir = HDIR_CREATE;
  965.         entries = 1;
  966.         rc = DosFindFirst(new_file, &hdir,
  967.             FILE_DIRECTORY | FILE_READONLY, &fb, sizeof(fb),
  968.             &entries, FIL_STANDARD);
  969.         if (rc == NO_ERROR) {
  970.             DosFindClose(hdir);
  971.             (void)strcpy(sofar, fb.achName);
  972.             sofar += strlen(sofar);
  973.         }
  974.         else
  975.             sofar += len;
  976.         if (next != end)
  977.             *sofar++ = SLASHC;
  978.         current = next + 1;
  979.     }
  980.     return 0;
  981. }
  982.  
  983. #else /* !SYS_OS2 */
  984.  
  985. static int
  986. case_correct_path(char *old_file, char *new_file)
  987. {
  988.     if (old_file != new_file)
  989.         (void)strcpy(new_file, old_file);
  990.     return 0;
  991. }
  992.  
  993. #endif /* !SYS_OS2 */
  994.  
  995. #endif /* !SYS_WINNT */
  996.  
  997. #endif /* OPT_CASELESS */
  998.  
  999. /* canonicalize a pathname, to eliminate extraneous /./, /../, and ////
  1000.     sequences.  only guaranteed to work for absolute pathnames */
  1001. static char *
  1002. canonpath(char *ss)
  1003. {
  1004.     char *p, *pp;
  1005.     char *s;
  1006.  
  1007.     TRACE(("canonpath '%s'\n", ss))
  1008.     if ((s = is_appendname(ss)) != 0)
  1009.         return (canonpath(s) != 0) ? ss : 0;
  1010.  
  1011.     s = ss;
  1012.  
  1013.     if (!*s)
  1014.         return s;
  1015.  
  1016. #if OPT_MSDOS_PATH
  1017. #if !OPT_CASELESS
  1018.     (void)mklower(ss);    /* MS-DOS is case-independent */
  1019. #endif
  1020.     if (is_slashc(*ss))
  1021.         *ss = SLASHC;
  1022.     /* pretend the drive designator isn't there */
  1023.     if ((s = is_msdos_drive(ss)) == 0)
  1024.         s = ss;
  1025. #endif
  1026.  
  1027. #if SYS_UNIX
  1028.     (void)home_path(s);
  1029. #endif
  1030.  
  1031. #if OPT_VMS_PATH
  1032.     /*
  1033.      * If the code in 'lengthen_path()', as well as the scattered calls on
  1034.      * 'fgetname()' are correct, the path given to this procedure should
  1035.      * be a fully-resolved VMS pathname.  The logic in filec.c will allow a
  1036.      * unix-style name, so we'll fall-thru if we find one.
  1037.      */
  1038.     if (is_vms_pathname(s, -TRUE)) {
  1039.         return mkupper(ss);
  1040.     }
  1041. #endif
  1042.  
  1043. #if SYS_UNIX || OPT_MSDOS_PATH || OPT_VMS_PATH
  1044.     if (!is_slashc(*s)) {
  1045.         mlforce("BUG: canonpath '%s'", s);
  1046.         return ss;
  1047.     }
  1048.     *s = SLASHC;
  1049.  
  1050.     /*
  1051.      * If the system supports symbolic links (most UNIX systems do), we
  1052.      * cannot do dead reckoning to resolve the pathname.  We've made this a
  1053.      * user-mode because some systems have problems with NFS timeouts which
  1054.      * can make running vile _slow_.
  1055.      */
  1056. #ifdef GMDRESOLVE_LINKS
  1057.     if (global_g_val(GMDRESOLVE_LINKS))
  1058.     {
  1059.         char temp[NFILEN];
  1060.         char *leaf;
  1061.         char *head = resolve_directory(s, &leaf);
  1062.         if (head != 0) {
  1063.             if (leaf != 0)
  1064.                 (void)strcpy(s, pathcat(temp, head, leaf));
  1065.             else
  1066.                 (void)strcpy(s, head);
  1067.         }
  1068.     }
  1069.     else
  1070. #endif
  1071.     {
  1072.     p = pp = s;
  1073.  
  1074. #if SYS_APOLLO
  1075.     if (!is_slashc(p[1])) {    /* could be something like "/usr" */
  1076.         char    *cwd = current_directory(FALSE);
  1077.         char    temp[NFILEN];
  1078.         if (!strncmp(cwd, "//", 2)
  1079.          && strlen(cwd) > 2
  1080.          && (p = strchr(cwd+2, '/')) != 0) {
  1081.             (void)strcpy(strcpy(temp, cwd) + (p+1-cwd), s);
  1082.             (void)strcpy(s, temp);
  1083.         }
  1084.     }
  1085.     p = s + 1;    /* allow for leading "//" */
  1086. #endif
  1087.  
  1088.     p++; pp++;    /* leave the leading slash */
  1089.     while (*pp) {
  1090.         if (is_slashc(*pp)) {
  1091.             pp++;
  1092.             continue;
  1093.         }
  1094.         if (*pp == '.' && is_slashc(*(pp+1))) {
  1095.             pp += 2;
  1096.             continue;
  1097.         }
  1098.         break;
  1099.     }
  1100.     while (*pp) {
  1101.         if (is_slashc(*pp)) {
  1102.             while (is_slashc(*(pp+1)))
  1103.                 pp++;
  1104.             if (p > s && !is_slashc(*(p-1)))
  1105.                 *p++ = SLASHC;
  1106.             if (*(pp+1) == '.') {
  1107.                 if (*(pp+2) == EOS) {
  1108.                     /* change "/." at end to "" */
  1109.                     *(p-1) = EOS;    /* and we're done */
  1110.                     break;
  1111.                 }
  1112.                 if (is_slashc(*(pp+2))) {
  1113.                     pp += 2;
  1114.                     continue;
  1115.                 } else if (*(pp+2) == '.' && (is_slashc(*(pp+3))
  1116.                             || *(pp+3) == EOS)) {
  1117.                     while (p-1 > s && is_slashc(*(p-1)))
  1118.                         p--;
  1119.                     while (p > s && !is_slashc(*(p-1)))
  1120.                         p--;
  1121.                     if (p == s)
  1122.                         *p++ = SLASHC;
  1123.                     pp += 3;
  1124.                     continue;
  1125.                 }
  1126.             }
  1127.             pp++;
  1128.             continue;
  1129.         } else {
  1130.             *p++ = *pp++;
  1131.         }
  1132.     }
  1133.     if (p > s && is_slashc(*(p-1)))
  1134.         p--;
  1135.     if (p == s)
  1136.         *p++ = SLASHC;
  1137.     *p = EOS;
  1138.     }
  1139. #endif    /* SYS_UNIX || SYS_MSDOS */
  1140.  
  1141. #if OPT_VMS_PATH
  1142.     if (!is_vms_pathname(ss, -TRUE)) {
  1143.         char *tt = skip_string(ss);
  1144.  
  1145.         /*
  1146.          * If we're not looking at "/" or some other path that ends
  1147.          * with a slash, see if we can match the path to a directory
  1148.          * file.  If so, force a slash on the end so that the unix2vms
  1149.          * conversion will show a directory.
  1150.          */
  1151.         if (tt[-1] != SLASHC) {
  1152.             struct stat sb;
  1153. #if SYS_VMS
  1154.             (void)strcpy(tt, ".DIR");
  1155. #else
  1156.             (void)mklower(ss);
  1157. #endif
  1158.             if ((stat(SL_TO_BSL(ss), &sb) >= 0)
  1159.              && S_ISDIR(sb.st_mode))
  1160.                 (void)strcpy(tt, "/");
  1161.             else
  1162.                 *tt = EOS;
  1163.         }
  1164.  
  1165.         /* FIXME: this is a hack to prevent this function from
  1166.          * returning device-level strings, since (at the moment) I
  1167.          * don't have anything that returns a list of the mounted
  1168.          * devices on a VMS system.
  1169.          */
  1170.         if (!strcmp(ss, "/")) {
  1171.             (void)strcpy(ss, current_directory(FALSE));
  1172.             if ((tt = strchr(ss, ':')) != 0)
  1173.                 (void)strcpy(tt+1, "[000000]");
  1174.             else
  1175.                 (void)strcat(ss, ":");
  1176.             (void)mkupper(ss);
  1177.         } else {
  1178.             unix2vms_path(ss, ss);
  1179.         }
  1180.     }
  1181. #endif
  1182.  
  1183. #if OPT_CASELESS
  1184.     case_correct_path(ss, ss);
  1185. #endif
  1186.  
  1187.     TRACE((" -> '%s' canonpath\n", ss))
  1188.     return ss;
  1189. }
  1190.  
  1191. char *
  1192. shorten_path(char *path, int keep_cwd)
  1193. {
  1194.     char    temp[NFILEN];
  1195.     char *cwd;
  1196.     char *ff;
  1197.     char *slp;
  1198.     char *f;
  1199. #if OPT_VMS_PATH
  1200.     char *dot;
  1201. #endif
  1202.  
  1203.     if (!path || *path == EOS)
  1204.         return path;
  1205.  
  1206.     if (isInternalName(path))
  1207.         return path;
  1208.  
  1209.     TRACE(("shorten '%s'\n", path))
  1210.     if ((f = is_appendname(path)) != 0)
  1211.         return (shorten_path(f, keep_cwd) != 0) ? path : 0;
  1212.  
  1213. #if OPT_VMS_PATH
  1214.     /*
  1215.      * This assumes that 'path' is in canonical form.
  1216.      */
  1217.     cwd = current_directory(FALSE);
  1218.     ff  = path;
  1219.     dot = 0;
  1220.     TRACE(("current '%s'\n", cwd))
  1221.  
  1222.     if ((slp = strchr(cwd, '[')) != 0
  1223.      && (slp == cwd
  1224.       || !strncmp(cwd, path, (SIZE_T)(slp-cwd)))) { /* same device? */
  1225.           ff += (slp-cwd);
  1226.         cwd = slp;
  1227.         (void)strcpy(temp, "[");    /* hoping for relative-path */
  1228.         while (*cwd && *ff) {
  1229.             if (*cwd != *ff) {
  1230.                 if (*cwd == ']' && *ff == '.') {
  1231.                     /* "[.DIRNAME]FILENAME.TYP;1" */
  1232.                     ;
  1233.                 } else if (*cwd == '.' && *ff == ']') {
  1234.                     /* "[-]FILENAME.TYP;1" */
  1235.                     while (*cwd != EOS) {
  1236.                         if (*cwd++ == '.')
  1237.                             (void)strcat(temp, "-");
  1238.                     }
  1239.                     (void)strcat(temp, "]");
  1240.                     ff++;
  1241.                 } else if (dot != 0) {
  1242.                     int diff = (ff - dot);
  1243.  
  1244.                     /* "[-.DIRNAME]FILENAME.TYP;1" */
  1245.                     while (*cwd != EOS) {
  1246.                         if (*cwd++ == '.')
  1247.                             (void)strcat(temp, "-");
  1248.                     }
  1249.                     while (dot != ff) {
  1250.                         if (*dot++ == '.')
  1251.                             (void)strcat(temp, "-");
  1252.                     }
  1253.                     (void)strcat(temp, ".");
  1254.                     ff -= (diff - 1);
  1255.                 }
  1256.                 break;
  1257.             } else if (*cwd == ']') {
  1258.                 (void)strcat(temp, cwd);
  1259.                 ff++;    /* path-leaf, if any */
  1260.                 break;
  1261.             }
  1262.  
  1263.             if (*ff == '.')
  1264.                 dot = ff;
  1265.             cwd++;
  1266.             ff++;
  1267.         }
  1268.     } else {
  1269.         *temp = EOS;        /* different device, cannot relate */
  1270.     }
  1271.  
  1272.     if (!strcmp(temp, "[]")        /* "[]FILENAME.TYP;1" */
  1273.      && !keep_cwd)
  1274.         *temp = EOS;
  1275.  
  1276.     (void) strcpy(path, strcat(temp, ff));
  1277.     TRACE(("     -> '%s' shorten\n", path))
  1278. #else
  1279. # if SYS_UNIX || OPT_MSDOS_PATH
  1280.     cwd = current_directory(FALSE);
  1281.     slp = ff = path;
  1282.     while (*cwd && *ff && *cwd == *ff) {
  1283.         if (is_slashc(*ff))
  1284.             slp = ff;
  1285.         cwd++;
  1286.         ff++;
  1287.     }
  1288.  
  1289.     /* if we reached the end of cwd, and we're at a path boundary,
  1290.         then the file must be under '.' */
  1291.     if (*cwd == EOS) {
  1292.         if (keep_cwd) {
  1293.             temp[0] = '.';
  1294.             temp[1] = SLASHC;
  1295.             temp[2] = EOS;
  1296.         } else
  1297.             *temp = EOS;
  1298.         if (is_slashc(*ff))
  1299.             return strcpy(path, strcat(temp, ff+1));
  1300.         if (slp == ff - 1)
  1301.             return strcpy(path, strcat(temp, ff));
  1302.     }
  1303.  
  1304.     /* if we mismatched during the first path component, we're done */
  1305.     if (slp == path)
  1306.         return path;
  1307.  
  1308.     /* if we mismatched in the last component of cwd, then the file
  1309.         is under '..' */
  1310.     if (last_slash(cwd) == 0)
  1311.         return strcpy(path, strcat(strcpy(temp, ".."), slp));
  1312.  
  1313.     /* we're off by more than just '..', so use absolute path */
  1314. # endif    /* SYS_UNIX || SYS_MSDOS */
  1315. #endif    /* OPT_VMS_PATH */
  1316.  
  1317.     return path;
  1318. }
  1319.  
  1320. #if OPT_VMS_PATH
  1321. static int
  1322. mixed_case(const char *path)
  1323. {
  1324.     register int c;
  1325.     int    had_upper = FALSE;
  1326.     int    had_lower = FALSE;
  1327.     while ((c = *path++) != EOS) {
  1328.         if (isLower(c))    had_lower = TRUE;
  1329.         if (isUpper(c))    had_upper = TRUE;
  1330.     }
  1331.     return (had_upper && had_lower);
  1332. }
  1333. #endif
  1334.  
  1335. /*
  1336.  * Undo nominal effect of 'shorten_path()'
  1337.  */
  1338. char *
  1339. lengthen_path(char *path)
  1340. {
  1341. #if SYS_VMS
  1342.     struct    FAB    my_fab;
  1343.     struct    NAM    my_nam;
  1344.     char        my_esa[NAM$C_MAXRSS];    /* expanded: sys$parse */
  1345.     char        my_rsa[NAM$C_MAXRSS];    /* result: sys$search */
  1346. #endif
  1347.     register int len;
  1348.     const char *cwd;
  1349.     char    *f;
  1350.     char    temp[NFILEN];
  1351. #if OPT_MSDOS_PATH
  1352.     char    drive;
  1353. #endif
  1354.  
  1355.     if ((f = is_appendname(path)) != 0)
  1356.         return (lengthen_path(f) != 0) ? path : 0;
  1357.  
  1358.     if ((f = path) == 0)
  1359.         return path;
  1360.  
  1361.     if (*path != EOS && isInternalName(path)) {
  1362. #if OPT_VMS_PATH
  1363.         /*
  1364.          * The conflict between VMS pathnames (e.g., "[-]") and Vile's
  1365.          * scratch-buffer names is a little ambiguous.  On VMS, though,
  1366.          * we'll have to give VMS pathnames the edge.  We cheat a little,
  1367.          * by exploiting the fact (?) that the system calls return paths
  1368.          * in uppercase only.
  1369.          */
  1370.         if (!is_vms_pathname(path, TRUE) && !mixed_case(path))
  1371. #endif
  1372.         return path;
  1373.     }
  1374.  
  1375. #if SYS_UNIX
  1376.     (void)home_path(f);
  1377. #endif
  1378.  
  1379. #if SYS_VMS
  1380.     /*
  1381.      * If the file exists, we can ask VMS to tell the full pathname.
  1382.      */
  1383.     if ((*path != EOS) && maybe_pathname(path)) {
  1384.         int    fd;
  1385.         long    status;
  1386.         char    temp[NFILEN],
  1387.             leaf[NFILEN];
  1388.         register char    *s;
  1389.  
  1390.         if (!strchr(path, '*') && !strchr(path, '?')) {
  1391.             if ((fd = open(SL_TO_BSL(path), O_RDONLY, 0)) >= 0) {
  1392.                 getname(fd, temp);
  1393.                 (void)close(fd);
  1394.                 return strcpy(path, temp);
  1395.             }
  1396.         }
  1397.  
  1398.         /*
  1399.          * Path either contains a wildcard, or the file does
  1400.          * not already exist.  Use the system parser to expand
  1401.          * the pathname components.
  1402.          */
  1403.         my_fab = cc$rms_fab;
  1404.         my_fab.fab$l_fop = FAB$M_NAM;
  1405.         my_fab.fab$l_nam = &my_nam;    /* FAB => NAM block    */
  1406.         my_fab.fab$l_dna = "";        /* Default-selection    */
  1407.         my_fab.fab$b_dns = strlen(my_fab.fab$l_dna);
  1408.  
  1409.         my_fab.fab$l_fna = path;
  1410.         my_fab.fab$b_fns = strlen(path);
  1411.  
  1412.         my_nam = cc$rms_nam;
  1413.         my_nam.nam$b_ess = NAM$C_MAXRSS;
  1414.         my_nam.nam$l_esa = my_esa;
  1415.         my_nam.nam$b_rss = NAM$C_MAXRSS;
  1416.         my_nam.nam$l_rsa = my_rsa;
  1417.  
  1418.         if ((status = sys$parse(&my_fab)) == RMS$_NORMAL) {
  1419.             char *s = my_esa;
  1420.             int len = my_nam.nam$b_esl;
  1421.             s[len] = EOS;
  1422.             if (len > 2) {
  1423.                 s = pathleaf(s);
  1424.                 if (!strcmp(s, ".;"))
  1425.                     *s = EOS;
  1426.             }
  1427.             return strcpy(path, my_esa);
  1428.         } else {
  1429.             /* FIXME: try to expand partial directory specs, etc. */
  1430.         }
  1431.     }
  1432. #else
  1433. # if OPT_VMS_PATH
  1434.     /* this is only for testing! */
  1435.     if (fakevms_filename(path))
  1436.         return path;
  1437. # endif
  1438. #endif
  1439.  
  1440. #if SYS_UNIX || OPT_MSDOS_PATH || OPT_VMS_PATH
  1441. #if OPT_MSDOS_PATH
  1442.     if ((f = is_msdos_drive(path)) != 0)
  1443.         drive = *path;
  1444.     else {
  1445.         drive = EOS;
  1446.         f = path;
  1447.     }
  1448. #endif
  1449.     if (!is_slashc(f[0])) {
  1450. #if OPT_MSDOS_PATH
  1451.  
  1452. #if OPT_UNC_PATH
  1453.         if ( drive == EOS ) {
  1454.             GetCurrentDirectory(sizeof(temp), temp);
  1455.             cwd = temp;
  1456.         }
  1457.         else
  1458. #endif
  1459.          cwd = curr_dir_on_drive(drive != EOS
  1460.                  ? drive
  1461.                 : curdrive());
  1462.  
  1463.         if (!cwd) {
  1464.             /* Drive will be unspecified with UNC Paths */
  1465.             if ( (temp[0] = drive) != EOS ) {
  1466. #if SYS_OS2_EMX
  1467.                 (void)strcpy(temp + 1, ":/");
  1468. #else
  1469.                 (void)strcpy(temp + 1, ":\\");
  1470. #endif
  1471.             }
  1472.             cwd = temp;
  1473.         }
  1474. #else
  1475.         cwd = current_directory(FALSE);
  1476.         if (!is_slashc(*cwd))
  1477.             return path;
  1478. #endif
  1479. #if OPT_VMS_PATH
  1480.         vms2unix_path(temp, cwd);
  1481. #else
  1482.         (void)strcpy(temp, cwd);
  1483. #endif
  1484.         len = strlen(temp);
  1485.         temp[len++] = SLASHC;
  1486.         (void)strcpy(temp + len, f);
  1487.         (void)strcpy(path, temp);
  1488.     }
  1489. #if OPT_MSDOS_PATH
  1490.     if (is_msdos_drive(path) == 0) { /* ensure that we have drive too */
  1491.         /* UNC paths have no drive */
  1492.         if ( curdrive() != 0 ) {
  1493.             temp[0] = curdrive();
  1494.             temp[1] = ':';
  1495.             (void)strcpy(temp+2, path);
  1496.             (void)strcpy(path, temp);
  1497.         }
  1498.     }
  1499. #endif
  1500. #endif    /* SYS_UNIX || SYS_MSDOS */
  1501.  
  1502.     return canonpath(path);
  1503. }
  1504.  
  1505. /*
  1506.  * Returns true if the argument looks like an absolute pathname (e.g., on
  1507.  * unix, begins with a '/').
  1508.  */
  1509. static int
  1510. is_absolute_pathname(char *path)
  1511. {
  1512.     char    *f;
  1513.     if ((f = is_appendname(path)) != 0)
  1514.         return is_pathname(f);
  1515.  
  1516. #if OPT_VMS_PATH
  1517.     if (is_vms_pathname(path, -TRUE)
  1518.      && (strchr(path, LBRACK) != 0
  1519.       || strchr(path, ':') != 0))
  1520.         return TRUE;
  1521. #endif
  1522.  
  1523. #if OPT_MSDOS_PATH
  1524.     if ((f = is_msdos_drive(path)) != 0)
  1525.         return is_absolute_pathname(f);
  1526. #endif
  1527.  
  1528. #if SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS
  1529. #if SYS_UNIX
  1530.     if (path[0] == '~')
  1531.         return TRUE;
  1532. #endif
  1533.     if (is_slashc(path[0]))
  1534.         return TRUE;
  1535. #endif    /* SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS */
  1536.  
  1537.     return FALSE;
  1538. }
  1539.  
  1540. /*
  1541.  * Returns true if the argument looks like a relative pathname (e.g., on
  1542.  * unix, begins with "./" or "../")
  1543.  */
  1544. static int
  1545. is_relative_pathname(const char *path)
  1546. {
  1547.     int    n;
  1548. #if OPT_VMS_PATH
  1549.     if (is_vms_pathname(path, -TRUE)
  1550.      && !strncmp(path, "[-", 2))
  1551.         return TRUE;
  1552. #endif
  1553. #if SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS
  1554.     n = 0;
  1555.     if (path[n++] == '.') {
  1556.         if (path[n] == '.')
  1557.             n++;
  1558.         if (is_slashc(path[n]))
  1559.             return TRUE;
  1560.     }
  1561. #endif    /* SYS_UNIX || OPT_MSDOS_PATH || SYS_VMS */
  1562.  
  1563.     return FALSE;
  1564. }
  1565.  
  1566. /*
  1567.  * Returns true if the argument looks more like a pathname than anything else.
  1568.  *
  1569.  * Notes:
  1570.  *    This makes a syntax-only test (e.g., at the beginning of the string).
  1571.  *    VMS can accept UNIX-style /-delimited pathnames.
  1572.  */
  1573. int
  1574. is_pathname(char *path)
  1575. {
  1576.     return is_relative_pathname(path)
  1577.        ||  is_absolute_pathname(path);
  1578. }
  1579.  
  1580. /*
  1581.  * A bit weaker than 'is_pathname()', checks to see if the string contains
  1582.  * path delimiters.
  1583.  */
  1584. int
  1585. maybe_pathname(char *fn)
  1586. {
  1587.     if (is_pathname(fn))    /* test the obvious stuff */
  1588.         return TRUE;
  1589. #if OPT_MSDOS_PATH
  1590.     if (is_msdos_drive(fn))
  1591.         return TRUE;
  1592. #endif
  1593.     if (last_slash(fn) != 0)
  1594.         return TRUE;
  1595. #if OPT_VMS_PATH
  1596.     while (*fn != EOS) {
  1597.         if (ispath(*fn) && !isident(*fn))
  1598.             return TRUE;
  1599.         fn++;
  1600.     }
  1601. #endif
  1602.     return FALSE;
  1603. }
  1604.  
  1605. /*
  1606.  * Returns the filename portion if the argument is an append-name (and not an
  1607.  * internal name!), otherwise null.
  1608.  */
  1609. char *
  1610. is_appendname(char *fn)
  1611. {
  1612.     if (fn != 0) {
  1613.         if (isAppendToName(fn)) {
  1614.             fn += 2;    /* skip the ">>" prefix */
  1615.             fn = skip_blanks(fn);
  1616.             if (!isInternalName(fn))
  1617.                 return fn;
  1618.         }
  1619.     }
  1620.     return 0;
  1621. }
  1622.  
  1623. /*
  1624.  * Returns true if the filename is either a scratch-name, or is the string that
  1625.  * we generate for the filename-field of [Help] and [Buffer List].  Use this
  1626.  * function rather than simple tests of '[' to make tests for VMS filenames
  1627.  * unambiguous.
  1628.  */
  1629. int
  1630. is_internalname(const char *fn)
  1631. {
  1632. #if OPT_VMS_PATH
  1633.     if (is_vms_pathname(fn, FALSE))
  1634.         return FALSE;
  1635. #endif
  1636.     if (!strcmp(fn, non_filename()))
  1637.         return TRUE;
  1638.     return (*fn == EOS) || is_scratchname(fn);
  1639. }
  1640.  
  1641. /*
  1642.  * Make the simple test only for bracketed name.  We only use this when we're
  1643.  * certain it's a buffer name.
  1644.  */
  1645. int
  1646. is_scratchname(const char *fn)
  1647. {
  1648.     return ((*fn == SCRTCH_LEFT[0]) && (fn[strlen(fn)-1] == SCRTCH_RIGHT[0]));
  1649. }
  1650.  
  1651. /*
  1652.  * Test if the given path is a directory
  1653.  */
  1654. int
  1655. is_directory(char * path)
  1656. {
  1657. #if OPT_VMS_PATH
  1658.     register char *s;
  1659. #endif
  1660.     struct    stat    sb;
  1661.  
  1662.     if (path == NULL || *path == EOS)
  1663.         return FALSE;
  1664.  
  1665. #if OPT_VMS_PATH
  1666.     if (is_vms_pathname(path, TRUE)) {
  1667.         return TRUE;
  1668.     }
  1669.  
  1670.     /* If the name doesn't look like a directory, there's no point in
  1671.      * wasting time doing a 'stat()' call.
  1672.      */
  1673.     s = vms_pathleaf(path);
  1674.     if ((s = strchr(s, '.')) != 0) {
  1675.         char    ftype[NFILEN];
  1676.         (void)mkupper(strcpy(ftype, s));
  1677.         if (strcmp(ftype, ".DIR")
  1678.          && strcmp(ftype, ".DIR;1"))
  1679.             return FALSE;
  1680.     }
  1681. #endif
  1682. #if OPT_UNC_PATH
  1683.     /*
  1684.      * WARNING: kludge alert!
  1685.      *
  1686.      * The problem here is that \\system\share, if it exists,
  1687.      * must be a directory.  However, due to a bug in the win32
  1688.      * stat function, it may be reported to exist (stat succeeds)
  1689.      * but that it is a file, not a directory.  So we special case
  1690.      * a stand-alone \\system\share name and force it to be reported
  1691.      * as a dir.
  1692.      */
  1693.     if (is_slashc(path[0]) && is_slashc(path[1])) {
  1694.         char *end = skip_string(path);
  1695.         int slashes = 0;
  1696.         if (end > path && is_slashc(end[-1]))
  1697.             end--;
  1698.         while (--end >= path) {
  1699.             if (is_slashc(*end))
  1700.                 slashes++;
  1701.         }
  1702.         if (slashes == 3)
  1703.             return 1;
  1704.     }
  1705. #endif
  1706.     return ( (stat(SL_TO_BSL(path), &sb) >= 0)
  1707. #if SYS_OS2 && CC_CSETPP
  1708.         && ((sb.st_mode & S_IFDIR) != 0)
  1709. #else
  1710.         && S_ISDIR(sb.st_mode)
  1711. #endif
  1712.       );
  1713.  
  1714. }
  1715.  
  1716. #if (SYS_UNIX||SYS_VMS||OPT_MSDOS_PATH) && OPT_PATHLOOKUP
  1717. /*
  1718.  * Parse the next entry in a list of pathnames, returning null only when no
  1719.  * more entries can be parsed.
  1720.  */
  1721. const char *
  1722. parse_pathlist(const char *list, char *result)
  1723. {
  1724.     if (list != NULL && *list != EOS) {
  1725.         register int    len = 0;
  1726.  
  1727.         while (*list && (*list != PATHCHR)) {
  1728.             if (len < NFILEN-1)
  1729.                 result[len++] = *list;
  1730.             list++;
  1731.         }
  1732.         if (len == 0)    /* avoid returning an empty-string */
  1733.             result[len++] = '.';
  1734.         result[len] = EOS;
  1735.  
  1736.         if (*list == PATHCHR)
  1737.             ++list;
  1738.     } else
  1739.         list = NULL;
  1740.     return list;
  1741. }
  1742. #endif    /* OPT_PATHLOOKUP */
  1743.  
  1744. #if SYS_WINNT && !CC_TURBO
  1745. /********                                               \\  opendir  //
  1746.  *                                                        ===========
  1747.  * opendir
  1748.  *
  1749.  * Description:
  1750.  *      Prepares to scan the file name entries in a directory.
  1751.  *
  1752.  * Arguments:   filename in NT format
  1753.  *
  1754.  * Returns:     pointer to a (malloc-ed) DIR structure.
  1755.  *
  1756.  * Joseph E. Greer      July 22 1992
  1757.  *
  1758.  ********/
  1759.  
  1760. DIR *
  1761. opendir(char * fname)
  1762. {
  1763.     char buf[MAX_PATH];
  1764.     DIR *od;
  1765.  
  1766.     (void)strcpy(buf, fname);
  1767.  
  1768.     if (!strcmp(buf, ".")) /* if its just a '.', replace with '*.*' */
  1769.         (void)strcpy(buf, "*.*");
  1770.     else {
  1771.         /* If the name ends with a slash, append '*.*' otherwise '\*.*' */
  1772.         if (is_slashc(buf[strlen(buf)-1]))
  1773.             (void)strcat(buf, "*.*");
  1774.         else
  1775.             (void)strcat(buf, "\\*.*");
  1776.     }
  1777.  
  1778.     /* allocate the structure to maintain currency */
  1779.     if ((od = typealloc(DIR)) == NULL)
  1780.         return NULL;
  1781.  
  1782.     /* Let's try to find a file matching the given name */
  1783.     if ((od->hFindFile = FindFirstFile(buf, &od->ffd))
  1784.         == INVALID_HANDLE_VALUE) {
  1785.         free(od);
  1786.         return NULL;
  1787.     }
  1788.     od->first = 1;
  1789.     return od;
  1790. }
  1791.  
  1792. /********                                               \\  readdir  //
  1793.  *                                                        ===========
  1794.  * readdir
  1795.  *
  1796.  * Description:
  1797.  *      Read a directory entry.
  1798.  *
  1799.  * Arguments:   a DIR pointer
  1800.  *
  1801.  * Returns:     A struct direct
  1802.  *
  1803.  * Joseph E. Greer      July 22 1992
  1804.  *
  1805.  ********/
  1806. DIRENT *
  1807. readdir(DIR *dirp)
  1808. {
  1809.     if (dirp->first)
  1810.         dirp->first = 0;
  1811.     else if (!FindNextFile(dirp->hFindFile, &dirp->ffd))
  1812.         return NULL;
  1813.     dirp->de.d_name = dirp->ffd.cFileName;
  1814.     return &dirp->de;
  1815. }
  1816.  
  1817. /********                                               \\  closedir  //
  1818.  *                                                        ===========
  1819.  * closedir
  1820.  *
  1821.  * Description:
  1822.  *      Close a directory entry.
  1823.  *
  1824.  * Arguments:   a DIR pointer
  1825.  *
  1826.  * Returns:     A struct direct
  1827.  *
  1828.  * Joseph E. Greer      July 22 1992
  1829.  *
  1830.  ********/
  1831. int
  1832. closedir(DIR *dirp)
  1833. {
  1834.     FindClose(dirp->hFindFile);
  1835.     free(dirp);
  1836.     return 0;
  1837. }
  1838.  
  1839. #endif /* SYS_WINNT */
  1840.  
  1841. #if OPT_MSDOS_PATH && !SYS_OS2_EMX
  1842. static char *slconv ( const char *f, char *t, char oc, char nc );
  1843. static char slconvpath[NFILEN * 2];
  1844.  
  1845. /*
  1846.  * Use this function to filter our internal '/' format pathnames to '\'
  1847.  * when invoking system calls (e.g., opendir, chdir).
  1848.  */
  1849. char *
  1850. sl_to_bsl(const char *p)
  1851. {
  1852.     size_t len;
  1853.     char *s = slconv(p, slconvpath, '/', '\\');
  1854.     if ((s = is_msdos_drive(s)) == 0)
  1855.         s = slconvpath;
  1856.     /* Trim trailing slash if it's not the first */
  1857.     if ((len = strlen(s)) > 1
  1858.      && is_slashc(s[len-1]))
  1859.         s[--len] = EOS;
  1860.     return slconvpath;
  1861. }
  1862.  
  1863. /*
  1864.  * Use this function to tidy up and put the path-slashes into internal form.
  1865.  */
  1866. #ifndef bsl_to_sl_inplace
  1867. void
  1868. bsl_to_sl_inplace(char *p)
  1869. {
  1870.     (void)slconv(p, p, '\\', '/');
  1871. }
  1872. #endif
  1873.  
  1874. static char *
  1875. slconv(const char *f, char *t, char oc, char nc)
  1876. {
  1877.     char *retp = t;
  1878.     while (*f) {
  1879.         if (*f == oc)
  1880.             *t = nc;
  1881.         else
  1882.             *t = *f;
  1883.         f++;
  1884.         t++;
  1885.     }
  1886.     *t-- = EOS;
  1887.  
  1888.     return retp;
  1889. }
  1890. #endif
  1891.  
  1892. #if OPT_VMS_PATH
  1893. /*
  1894.  * Strip the VMS version number, so the resulting path implicitly applies to
  1895.  * the current version.
  1896.  */
  1897. char *
  1898. strip_version(char *path)
  1899. {
  1900.     char *verp = strrchr(path, ';');
  1901.     if (verp != 0)
  1902.         *verp = EOS;
  1903.     return path;
  1904. }
  1905. #endif
  1906.  
  1907. #if NO_LEAKS
  1908. void
  1909. path_leaks(void)
  1910. {
  1911. #if SYS_UNIX
  1912.     while (user_paths != NULL) {
  1913.         register UPATH *paths = user_paths;
  1914.         user_paths = paths->next;
  1915.         free(paths->name);
  1916.         free(paths->path);
  1917.         free((char *)paths);
  1918.     }
  1919. #endif
  1920. }
  1921. #endif    /* NO_LEAKS */
  1922.