xref: /openbsd/bin/csh/dir.c (revision 5b133f3f)
1 /*	$OpenBSD: dir.c,v 1.26 2023/03/08 04:43:04 guenther Exp $	*/
2 /*	$NetBSD: dir.c,v 1.9 1995/03/21 09:02:42 cgd Exp $	*/
3 
4 /*-
5  * Copyright (c) 1980, 1991, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <limits.h>
39 #include <stdarg.h>
40 
41 #include "csh.h"
42 #include "dir.h"
43 #include "extern.h"
44 
45 /* Directory management. */
46 
47 static struct directory
48 		*dfind(Char *);
49 static Char	*dfollow(Char *);
50 static void	 printdirs(void);
51 static Char	*dgoto(Char *);
52 static void	 dnewcwd(struct directory *);
53 static void	 dset(Char *);
54 
55 struct directory dhead;		/* "head" of loop */
56 struct directory *dcwd;		/* the one we are in now */
57 int     printd;			/* force name to be printed */
58 
59 static int dirflag = 0;
60 
61 /*
62  * dinit - initialize current working directory
63  */
64 void
dinit(Char * hp)65 dinit(Char *hp)
66 {
67     char *tcp;
68     Char *cp = NULL;
69     struct directory *dp;
70     char    path[PATH_MAX];
71     static const char emsg[] = "csh: Trying to start from \"%s\"\n";
72 
73     /* Don't believe the login shell home, because it may be a symlink */
74     tcp = getcwd(path, PATH_MAX);
75     if (tcp == NULL || *tcp == '\0') {
76 	(void) fprintf(csherr, "csh: %s\n", strerror(errno));
77 	if (hp && *hp) {
78 	    tcp = short2str(hp);
79 	    if (chdir(tcp) == 0)
80 		cp = hp;
81 	    (void) fprintf(csherr, emsg, vis_str(hp));
82 	}
83     }
84     else {
85 	struct stat swd, shp;
86 
87 	if (stat(tcp, &swd) == -1) {
88 	    (void) fprintf(csherr, "csh: %s: %s\n", tcp, strerror(errno));
89 	} else {
90 	    /*
91 	     * See if $HOME is the working directory we got and use that
92 	     */
93 	    if (hp && *hp && stat(short2str(hp), &shp) != -1 &&
94 		swd.st_dev == shp.st_dev && swd.st_ino == shp.st_ino)
95 		cp = hp;
96 	    else {
97 		char   *cwd;
98 
99 		/*
100 		 * use PWD if we have it (for subshells)
101 		 */
102 		if ((cwd = getenv("PWD")) != NULL) {
103 		    if (stat(cwd, &shp) != -1 && swd.st_dev == shp.st_dev &&
104 			swd.st_ino == shp.st_ino)
105 			tcp = cwd;
106 		}
107 		cp = dcanon(SAVE(tcp), STRNULL);
108 	    }
109 	}
110     }
111 
112     if (cp == NULL) {
113 	(void) fprintf(csherr, emsg, "/");
114 	if (chdir("/") == -1)
115 	    /* I am not even try to print an error message! */
116 	    xexit(1);
117 	cp = SAVE("/");
118     }
119 
120     dp = xcalloc(1, sizeof(struct directory));
121     dp->di_name = Strsave(cp);
122     dp->di_count = 0;
123     dhead.di_next = dhead.di_prev = dp;
124     dp->di_next = dp->di_prev = &dhead;
125     printd = 0;
126     dnewcwd(dp);
127 }
128 
129 static void
dset(Char * dp)130 dset(Char *dp)
131 {
132     /*
133      * Don't call set() directly cause if the directory contains ` or
134      * other junk characters glob will fail.
135      */
136     Char **vec = xreallocarray(NULL, 2, sizeof(*vec));
137 
138     vec[0] = Strsave(dp);
139     vec[1] = 0;
140     setq(STRcwd, vec, &shvhed);
141     Setenv(STRPWD, dp);
142 }
143 
144 #define DIR_LONG 1
145 #define DIR_VERT 2
146 #define DIR_LINE 4
147 
148 static void
skipargs(Char *** v,char * str)149 skipargs(Char ***v, char *str)
150 {
151     Char  **n = *v, *s;
152 
153     dirflag = 0;
154     for (n++; *n != NULL && (*n)[0] == '-'; n++)
155 	for (s = &((*n)[1]); *s; s++)
156 	    switch (*s) {
157 	    case 'l':
158 		dirflag |= DIR_LONG;
159 		break;
160 	    case 'v':
161 		dirflag |= DIR_VERT;
162 		break;
163 	    case 'n':
164 		dirflag |= DIR_LINE;
165 		break;
166 	    default:
167 		stderror(ERR_DIRUS, vis_str(**v), str);
168 		break;
169 	    }
170     *v = n;
171 }
172 
173 /*
174  * dodirs - list all directories in directory loop
175  */
176 void
dodirs(Char ** v,struct command * t)177 dodirs(Char **v, struct command *t)
178 {
179     skipargs(&v, "");
180 
181     if (*v != NULL)
182 	stderror(ERR_DIRUS, "dirs", "");
183     printdirs();
184 }
185 
186 static void
printdirs(void)187 printdirs(void)
188 {
189     struct directory *dp;
190     Char   *s, *hp = value(STRhome);
191     int     idx, len, cur;
192 
193     if (*hp == '\0')
194 	hp = NULL;
195     dp = dcwd;
196     idx = 0;
197     cur = 0;
198     do {
199 	if (dp == &dhead)
200 	    continue;
201 	if (dirflag & DIR_VERT) {
202 	    (void) fprintf(cshout, "%d\t", idx++);
203 	    cur = 0;
204 	}
205 	if (!(dirflag & DIR_LONG) && hp != NULL && !eq(hp, STRslash) &&
206 	    (len = Strlen(hp), Strncmp(hp, dp->di_name, len) == 0) &&
207 	    (dp->di_name[len] == '\0' || dp->di_name[len] == '/'))
208 	    len = Strlen(s = (dp->di_name + len)) + 2;
209 	else
210 	    len = Strlen(s = dp->di_name) + 1;
211 
212 	cur += len;
213 	if ((dirflag & DIR_LINE) && cur >= 80 - 1 && len < 80) {
214 	    (void) fprintf(cshout, "\n");
215 	    cur = len;
216 	}
217 	(void) fprintf(cshout, s != dp->di_name ? "~%s%c" : "%s%c",
218 		vis_str(s), (dirflag & DIR_VERT) ? '\n' : ' ');
219     } while ((dp = dp->di_prev) != dcwd);
220     if (!(dirflag & DIR_VERT))
221 	(void) fprintf(cshout, "\n");
222 }
223 
224 void
dtildepr(Char * home,Char * dir)225 dtildepr(Char *home, Char *dir)
226 {
227 
228     if (!eq(home, STRslash) && prefix(home, dir))
229 	(void) fprintf(cshout, "~%s", vis_str(dir + Strlen(home)));
230     else
231 	(void) fprintf(cshout, "%s", vis_str(dir));
232 }
233 
234 void
dtilde(void)235 dtilde(void)
236 {
237     struct directory *d = dcwd;
238 
239     do {
240 	if (d == &dhead)
241 	    continue;
242 	d->di_name = dcanon(d->di_name, STRNULL);
243     } while ((d = d->di_prev) != dcwd);
244 
245     dset(dcwd->di_name);
246 }
247 
248 
249 /* dnormalize():
250  *	If the name starts with . or .. then we might need to normalize
251  *	it depending on the symbolic link flags
252  */
253 Char   *
dnormalize(Char * cp)254 dnormalize(Char *cp)
255 {
256 
257 #define UC (unsigned char)
258 #define ISDOT(c) (UC(c)[0] == '.' && ((UC(c)[1] == '\0') || (UC(c)[1] == '/')))
259 #define ISDOTDOT(c) (UC(c)[0] == '.' && ISDOT(&((c)[1])))
260 
261     if ((unsigned char) cp[0] == '/')
262 	return (Strsave(cp));
263 
264     if (adrof(STRignore_symlinks)) {
265 	int     dotdot = 0;
266 	Char   *dp, *cwd;
267 	size_t	len;
268 
269 	len = (size_t) (Strlen(dcwd->di_name) + 3);
270 	cwd = xreallocarray(NULL, len, sizeof(Char));
271 	(void) Strlcpy(cwd, dcwd->di_name, len);
272 
273 	/*
274 	 * Ignore . and count ..'s
275 	 */
276 	while (*cp) {
277 	    if (ISDOT(cp)) {
278 		if (*++cp)
279 		    cp++;
280 	    }
281 	    else if (ISDOTDOT(cp)) {
282 		dotdot++;
283 		cp += 2;
284 		if (*cp)
285 		    cp++;
286 	    }
287 	    else
288 		break;
289 	}
290 	while (dotdot > 0)
291 	    if ((dp = Strrchr(cwd, '/'))) {
292 		*dp = '\0';
293 		dotdot--;
294 	    }
295 	    else
296 		break;
297 
298 	if (*cp) {
299 	    cwd[dotdot = Strlen(cwd)] = '/';
300 	    cwd[dotdot + 1] = '\0';
301 	    dp = Strspl(cwd, cp);
302 	    free(cwd);
303 	    return dp;
304 	}
305 	else {
306 	    if (!*cwd) {
307 		cwd[0] = '/';
308 		cwd[1] = '\0';
309 	    }
310 	    return cwd;
311 	}
312     }
313     return Strsave(cp);
314 }
315 
316 /*
317  * dochngd - implement chdir command.
318  */
319 void
dochngd(Char ** v,struct command * t)320 dochngd(Char **v, struct command *t)
321 {
322     Char *cp;
323     struct directory *dp;
324 
325     skipargs(&v, " [<dir>]");
326     printd = 0;
327     if (*v == NULL) {
328 	if ((cp = value(STRhome)) == NULL || *cp == 0)
329 	    stderror(ERR_NAME | ERR_NOHOMEDIR);
330 	if (chdir(short2str(cp)) == -1)
331 	    stderror(ERR_NAME | ERR_CANTCHANGE);
332 	cp = Strsave(cp);
333     }
334     else if (v[1] != NULL) {
335 	stderror(ERR_NAME | ERR_TOOMANY);
336 	/* NOTREACHED */
337 	return;
338     }
339     else if ((dp = dfind(*v)) != 0) {
340 	char   *tmp;
341 
342 	printd = 1;
343 	if (chdir(tmp = short2str(dp->di_name)) == -1)
344 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
345 	dcwd->di_prev->di_next = dcwd->di_next;
346 	dcwd->di_next->di_prev = dcwd->di_prev;
347 	dfree(dcwd);
348 	dnewcwd(dp);
349 	return;
350     }
351     else
352 	cp = dfollow(*v);
353     dp = xcalloc(1, sizeof(struct directory));
354     dp->di_name = cp;
355     dp->di_count = 0;
356     dp->di_next = dcwd->di_next;
357     dp->di_prev = dcwd->di_prev;
358     dp->di_prev->di_next = dp;
359     dp->di_next->di_prev = dp;
360     dfree(dcwd);
361     dnewcwd(dp);
362 }
363 
364 static Char *
dgoto(Char * cp)365 dgoto(Char *cp)
366 {
367     Char   *dp;
368 
369     if (*cp != '/') {
370 	Char *p, *q;
371 	int     cwdlen;
372 
373 	for (p = dcwd->di_name; *p++;)
374 	    continue;
375 	if ((cwdlen = p - dcwd->di_name - 1) == 1)	/* root */
376 	    cwdlen = 0;
377 	for (p = cp; *p++;)
378 	    continue;
379 	dp = xreallocarray(NULL, (cwdlen + (p - cp) + 1), sizeof(Char));
380 	for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
381 	    continue;
382 	if (cwdlen)
383 	    p[-1] = '/';
384 	else
385 	    p--;		/* don't add a / after root */
386 	for (q = cp; (*p++ = *q++) != '\0';)
387 	    continue;
388 	free(cp);
389 	cp = dp;
390 	dp += cwdlen;
391     }
392     else
393 	dp = cp;
394 
395     cp = dcanon(cp, dp);
396     return cp;
397 }
398 
399 /*
400  * dfollow - change to arg directory; fall back on cdpath if not valid
401  */
402 static Char *
dfollow(Char * cp)403 dfollow(Char *cp)
404 {
405     Char *dp;
406     struct varent *c;
407     char    ebuf[PATH_MAX];
408     int serrno;
409 
410     cp = globone(cp, G_ERROR);
411     /*
412      * if we are ignoring symlinks, try to fix relatives now.
413      */
414     dp = dnormalize(cp);
415     if (chdir(short2str(dp)) >= 0) {
416 	free(cp);
417 	return dgoto(dp);
418     }
419     else {
420 	free(dp);
421 	if (chdir(short2str(cp)) >= 0)
422 	    return dgoto(cp);
423 	serrno = errno;
424     }
425 
426     if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
427 	&& (c = adrof(STRcdpath))) {
428 	Char  **cdp;
429 	Char *p;
430 	Char    buf[PATH_MAX];
431 
432 	for (cdp = c->vec; *cdp; cdp++) {
433 	    for (dp = buf, p = *cdp; (*dp++ = *p++) != '\0';)
434 		continue;
435 	    dp[-1] = '/';
436 	    for (p = cp; (*dp++ = *p++) != '\0';)
437 		continue;
438 	    if (chdir(short2str(buf)) >= 0) {
439 		printd = 1;
440 		free(cp);
441 		cp = Strsave(buf);
442 		return dgoto(cp);
443 	    }
444 	}
445     }
446     dp = value(cp);
447     if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
448 	free(cp);
449 	cp = Strsave(dp);
450 	printd = 1;
451 	return dgoto(cp);
452     }
453     (void) strlcpy(ebuf, short2str(cp), sizeof ebuf);
454     free(cp);
455     stderror(ERR_SYSTEM, ebuf, strerror(serrno));
456     return (NULL);
457 }
458 
459 
460 /*
461  * dopushd - push new directory onto directory stack.
462  *	with no arguments exchange top and second.
463  *	with numeric argument (+n) bring it to top.
464  */
465 void
dopushd(Char ** v,struct command * t)466 dopushd(Char **v, struct command *t)
467 {
468     struct directory *dp;
469 
470     skipargs(&v, " [<dir>|+<n>]");
471     printd = 1;
472     if (*v == NULL) {
473 	char   *tmp;
474 
475 	if ((dp = dcwd->di_prev) == &dhead)
476 	    dp = dhead.di_prev;
477 	if (dp == dcwd)
478 	    stderror(ERR_NAME | ERR_NODIR);
479 	if (chdir(tmp = short2str(dp->di_name)) == -1)
480 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
481 	dp->di_prev->di_next = dp->di_next;
482 	dp->di_next->di_prev = dp->di_prev;
483 	dp->di_next = dcwd->di_next;
484 	dp->di_prev = dcwd;
485 	dcwd->di_next->di_prev = dp;
486 	dcwd->di_next = dp;
487     }
488     else if (v[1] != NULL) {
489 	stderror(ERR_NAME | ERR_TOOMANY);
490 	/* NOTREACHED */
491 	return;
492     }
493     else if ((dp = dfind(*v)) != NULL) {
494 	char   *tmp;
495 
496 	if (chdir(tmp = short2str(dp->di_name)) == -1)
497 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
498     }
499     else {
500 	Char *ccp;
501 
502 	ccp = dfollow(*v);
503 	dp = xcalloc(1, sizeof(struct directory));
504 	dp->di_name = ccp;
505 	dp->di_count = 0;
506 	dp->di_prev = dcwd;
507 	dp->di_next = dcwd->di_next;
508 	dcwd->di_next = dp;
509 	dp->di_next->di_prev = dp;
510     }
511     dnewcwd(dp);
512 }
513 
514 /*
515  * dfind - find a directory if specified by numeric (+n) argument
516  */
517 static struct directory *
dfind(Char * cp)518 dfind(Char *cp)
519 {
520     struct directory *dp;
521     int i;
522     Char *ep;
523 
524     if (*cp++ != '+')
525 	return (0);
526     for (ep = cp; Isdigit(*ep); ep++)
527 	continue;
528     if (*ep)
529 	return (0);
530     i = getn(cp);
531     if (i <= 0)
532 	return (0);
533     for (dp = dcwd; i != 0; i--) {
534 	if ((dp = dp->di_prev) == &dhead)
535 	    dp = dp->di_prev;
536 	if (dp == dcwd)
537 	    stderror(ERR_NAME | ERR_DEEP);
538     }
539     return (dp);
540 }
541 
542 /*
543  * dopopd - pop a directory out of the directory stack
544  *	with a numeric argument just discard it.
545  */
546 void
dopopd(Char ** v,struct command * t)547 dopopd(Char **v, struct command *t)
548 {
549     struct directory *dp, *p = NULL;
550 
551     skipargs(&v, " [+<n>]");
552     printd = 1;
553     if (*v == NULL)
554 	dp = dcwd;
555     else if (v[1] != NULL) {
556 	stderror(ERR_NAME | ERR_TOOMANY);
557 	/* NOTREACHED */
558 	return;
559     }
560     else if ((dp = dfind(*v)) == 0)
561 	stderror(ERR_NAME | ERR_BADDIR);
562     if (dp->di_prev == &dhead && dp->di_next == &dhead)
563 	stderror(ERR_NAME | ERR_EMPTY);
564     if (dp == dcwd) {
565 	char   *tmp;
566 
567 	if ((p = dp->di_prev) == &dhead)
568 	    p = dhead.di_prev;
569 	if (chdir(tmp = short2str(p->di_name)) == -1)
570 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
571     }
572     dp->di_prev->di_next = dp->di_next;
573     dp->di_next->di_prev = dp->di_prev;
574     if (dp == dcwd)
575 	dnewcwd(p);
576     else {
577 	printdirs();
578     }
579     dfree(dp);
580 }
581 
582 /*
583  * dfree - free the directory (or keep it if it still has ref count)
584  */
585 void
dfree(struct directory * dp)586 dfree(struct directory *dp)
587 {
588 
589     if (dp->di_count != 0) {
590 	dp->di_next = dp->di_prev = 0;
591     }
592     else {
593 	free((char *) dp->di_name);
594 	free(dp);
595     }
596 }
597 
598 /*
599  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
600  *	we are of course assuming that the file system is standardly
601  *	constructed (always have ..'s, directories have links)
602  */
603 Char   *
dcanon(Char * cp,Char * p)604 dcanon(Char *cp, Char *p)
605 {
606     Char *sp;
607     Char *p1, *p2;	/* general purpose */
608     bool    slash;
609 
610     Char    link[PATH_MAX];
611     char    tlink[PATH_MAX];
612     int     cc;
613     Char   *newcp;
614 
615     /*
616      * christos: if the path given does not start with a slash prepend cwd. If
617      * cwd does not start with a path or the result would be too long abort().
618      */
619     if (*cp != '/') {
620 	Char    tmpdir[PATH_MAX];
621 
622 	p1 = value(STRcwd);
623 	if (p1 == NULL || *p1 != '/')
624 	    abort();
625 	if (Strlen(p1) + Strlen(cp) + 1 >= PATH_MAX)
626 	    abort();
627 	(void) Strlcpy(tmpdir, p1, sizeof tmpdir/sizeof(Char));
628 	(void) Strlcat(tmpdir, STRslash, sizeof tmpdir/sizeof(Char));
629 	(void) Strlcat(tmpdir, cp, sizeof tmpdir/sizeof(Char));
630 	free(cp);
631 	cp = p = Strsave(tmpdir);
632     }
633 
634     while (*p) {		/* for each component */
635 	sp = p;			/* save slash address */
636 	while (*++p == '/')	/* flush extra slashes */
637 	    continue;
638 	if (p != ++sp)
639 	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
640 		continue;
641 	p = sp;			/* save start of component */
642 	slash = 0;
643 	while (*p)		/* find next slash or end of path */
644 	    if (*++p == '/') {
645 		slash = 1;
646 		*p = 0;
647 		break;
648 	    }
649 
650 	if (*sp == '\0')	/* if component is null */
651 	    if (--sp == cp)	/* if path is one char (i.e. /) */
652 		break;
653 	    else
654 		*sp = '\0';
655 	else if (sp[0] == '.' && sp[1] == 0) {
656 	    if (slash) {
657 		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
658 		    continue;
659 		p = --sp;
660 	    }
661 	    else if (--sp != cp)
662 		*sp = '\0';
663 	}
664 	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
665 	    /*
666 	     * We have something like "yyy/xxx/..", where "yyy" can be null or
667 	     * a path starting at /, and "xxx" is a single component. Before
668 	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
669 	     * symbolic link.
670 	     */
671 	    *--sp = 0;		/* form the pathname for readlink */
672 	    if (sp != cp && !adrof(STRignore_symlinks) &&
673 		(cc = readlink(short2str(cp), tlink,
674 			       sizeof tlink-1)) >= 0) {
675 		tlink[cc] = '\0';
676 		(void) Strlcpy(link, str2short(tlink), sizeof link/sizeof(Char));
677 
678 		if (slash)
679 		    *p = '/';
680 		/*
681 		 * Point p to the '/' in "/..", and restore the '/'.
682 		 */
683 		*(p = sp) = '/';
684 		/*
685 		 * find length of p
686 		 */
687 		for (p1 = p; *p1++;)
688 		    continue;
689 		if (*link != '/') {
690 		    /*
691 		     * Relative path, expand it between the "yyy/" and the
692 		     * "/..". First, back sp up to the character past "yyy/".
693 		     */
694 		    while (*--sp != '/')
695 			continue;
696 		    sp++;
697 		    *sp = 0;
698 		    /*
699 		     * New length is "yyy/" + link + "/.." and rest
700 		     */
701 		    p1 = newcp = xreallocarray(NULL,
702 			(sp - cp) + cc + (p1 - p), sizeof(Char));
703 		    /*
704 		     * Copy new path into newcp
705 		     */
706 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
707 			continue;
708 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
709 			continue;
710 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
711 			continue;
712 		    /*
713 		     * Restart canonicalization at expanded "/xxx".
714 		     */
715 		    p = sp - cp - 1 + newcp;
716 		}
717 		else {
718 		    /*
719 		     * New length is link + "/.." and rest
720 		     */
721 		    p1 = newcp = xreallocarray(NULL, cc + (p1 - p),
722 		        sizeof(Char));
723 		    /*
724 		     * Copy new path into newcp
725 		     */
726 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
727 			continue;
728 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
729 			continue;
730 		    /*
731 		     * Restart canonicalization at beginning
732 		     */
733 		    p = newcp;
734 		}
735 		free(cp);
736 		cp = newcp;
737 		continue;	/* canonicalize the link */
738 	    }
739 	    *sp = '/';
740 	    if (sp != cp)
741 		while (*--sp != '/')
742 		    continue;
743 	    if (slash) {
744 		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
745 		    continue;
746 		p = sp;
747 	    }
748 	    else if (cp == sp)
749 		*++sp = '\0';
750 	    else
751 		*sp = '\0';
752 	}
753 	else {			/* normal dir name (not . or .. or nothing) */
754 
755 	    if (sp != cp && adrof(STRchase_symlinks) &&
756 		!adrof(STRignore_symlinks) &&
757 		(cc = readlink(short2str(cp), tlink,
758 			       sizeof tlink-1)) >= 0) {
759 		tlink[cc] = '\0';
760 		(void) Strlcpy(link, str2short(tlink), sizeof link/sizeof(Char));
761 
762 		/*
763 		 * restore the '/'.
764 		 */
765 		if (slash)
766 		    *p = '/';
767 
768 		/*
769 		 * point sp to p (rather than backing up).
770 		 */
771 		sp = p;
772 
773 		/*
774 		 * find length of p
775 		 */
776 		for (p1 = p; *p1++;)
777 		    continue;
778 		if (*link != '/') {
779 		    /*
780 		     * Relative path, expand it between the "yyy/" and the
781 		     * remainder. First, back sp up to the character past
782 		     * "yyy/".
783 		     */
784 		    while (*--sp != '/')
785 			continue;
786 		    sp++;
787 		    *sp = 0;
788 		    /*
789 		     * New length is "yyy/" + link + "/.." and rest
790 		     */
791 		    p1 = newcp = xreallocarray(NULL,
792 			  (sp - cp) + cc + (p1 - p), sizeof(Char));
793 		    /*
794 		     * Copy new path into newcp
795 		     */
796 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
797 			continue;
798 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
799 			continue;
800 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
801 			continue;
802 		    /*
803 		     * Restart canonicalization at expanded "/xxx".
804 		     */
805 		    p = sp - cp - 1 + newcp;
806 		}
807 		else {
808 		    /*
809 		     * New length is link + the rest
810 		     */
811 		    p1 = newcp = xreallocarray(NULL, cc + (p1 - p), sizeof(Char));
812 		    /*
813 		     * Copy new path into newcp
814 		     */
815 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
816 			continue;
817 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
818 			continue;
819 		    /*
820 		     * Restart canonicalization at beginning
821 		     */
822 		    p = newcp;
823 		}
824 		free(cp);
825 		cp = newcp;
826 		continue;	/* canonicalize the link */
827 	    }
828 	    if (slash)
829 		*p = '/';
830 	}
831     }
832 
833     /*
834      * fix home...
835      */
836     p1 = value(STRhome);
837     cc = Strlen(p1);
838     /*
839      * See if we're not in a subdir of STRhome
840      */
841     if (p1 && *p1 == '/' &&
842 	(Strncmp(p1, cp, cc) != 0 || (cp[cc] != '/' && cp[cc] != '\0'))) {
843 	static ino_t home_ino = -1;
844 	static dev_t home_dev = -1;
845 	static Char *home_ptr = NULL;
846 	struct stat statbuf;
847 
848 	/*
849 	 * Get dev and ino of STRhome
850 	 */
851 	if (home_ptr != p1 &&
852 	    stat(short2str(p1), &statbuf) != -1) {
853 	    home_dev = statbuf.st_dev;
854 	    home_ino = statbuf.st_ino;
855 	    home_ptr = p1;
856 	}
857 	/*
858 	 * Start comparing dev & ino backwards
859 	 */
860 	Strlcpy(link, cp, sizeof link/sizeof(Char));
861 	p2 = link;
862 	for (sp = NULL; *p2 && stat(short2str(p2), &statbuf) != -1;) {
863 	    if (statbuf.st_dev == home_dev &&
864 		statbuf.st_ino == home_ino) {
865 		sp = (Char *) - 1;
866 		break;
867 	    }
868 	    if ((sp = Strrchr(p2, '/')) != NULL)
869 		*sp = '\0';
870 	}
871 	/*
872 	 * See if we found it
873 	 */
874 	if (*p2 && sp == (Char *) -1) {
875 	    /*
876 	     * Use STRhome to make '~' work
877 	     */
878 	    newcp = Strspl(p1, cp + Strlen(p2));
879 	    free(cp);
880 	    cp = newcp;
881 	}
882     }
883     return cp;
884 }
885 
886 
887 /*
888  * dnewcwd - make a new directory in the loop the current one
889  */
890 static void
dnewcwd(struct directory * dp)891 dnewcwd(struct directory *dp)
892 {
893     dcwd = dp;
894     dset(dcwd->di_name);
895     if (printd && !(adrof(STRpushdsilent)))
896 	printdirs();
897 }
898