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