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