xref: /openbsd-src/bin/csh/dir.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: dir.c,v 1.25 2020/08/30 22:23:47 mortimer 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
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
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
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
177 /*ARGSUSED*/
178 dodirs(Char **v, struct command *t)
179 {
180     skipargs(&v, "");
181 
182     if (*v != NULL)
183 	stderror(ERR_DIRUS, "dirs", "");
184     printdirs();
185 }
186 
187 static void
188 printdirs(void)
189 {
190     struct directory *dp;
191     Char   *s, *hp = value(STRhome);
192     int     idx, len, cur;
193 
194     if (*hp == '\0')
195 	hp = NULL;
196     dp = dcwd;
197     idx = 0;
198     cur = 0;
199     do {
200 	if (dp == &dhead)
201 	    continue;
202 	if (dirflag & DIR_VERT) {
203 	    (void) fprintf(cshout, "%d\t", idx++);
204 	    cur = 0;
205 	}
206 	if (!(dirflag & DIR_LONG) && hp != NULL && !eq(hp, STRslash) &&
207 	    (len = Strlen(hp), Strncmp(hp, dp->di_name, len) == 0) &&
208 	    (dp->di_name[len] == '\0' || dp->di_name[len] == '/'))
209 	    len = Strlen(s = (dp->di_name + len)) + 2;
210 	else
211 	    len = Strlen(s = dp->di_name) + 1;
212 
213 	cur += len;
214 	if ((dirflag & DIR_LINE) && cur >= 80 - 1 && len < 80) {
215 	    (void) fprintf(cshout, "\n");
216 	    cur = len;
217 	}
218 	(void) fprintf(cshout, s != dp->di_name ? "~%s%c" : "%s%c",
219 		vis_str(s), (dirflag & DIR_VERT) ? '\n' : ' ');
220     } while ((dp = dp->di_prev) != dcwd);
221     if (!(dirflag & DIR_VERT))
222 	(void) fprintf(cshout, "\n");
223 }
224 
225 void
226 dtildepr(Char *home, Char *dir)
227 {
228 
229     if (!eq(home, STRslash) && prefix(home, dir))
230 	(void) fprintf(cshout, "~%s", vis_str(dir + Strlen(home)));
231     else
232 	(void) fprintf(cshout, "%s", vis_str(dir));
233 }
234 
235 void
236 dtilde(void)
237 {
238     struct directory *d = dcwd;
239 
240     do {
241 	if (d == &dhead)
242 	    continue;
243 	d->di_name = dcanon(d->di_name, STRNULL);
244     } while ((d = d->di_prev) != dcwd);
245 
246     dset(dcwd->di_name);
247 }
248 
249 
250 /* dnormalize():
251  *	If the name starts with . or .. then we might need to normalize
252  *	it depending on the symbolic link flags
253  */
254 Char   *
255 dnormalize(Char *cp)
256 {
257 
258 #define UC (unsigned char)
259 #define ISDOT(c) (UC(c)[0] == '.' && ((UC(c)[1] == '\0') || (UC(c)[1] == '/')))
260 #define ISDOTDOT(c) (UC(c)[0] == '.' && ISDOT(&((c)[1])))
261 
262     if ((unsigned char) cp[0] == '/')
263 	return (Strsave(cp));
264 
265     if (adrof(STRignore_symlinks)) {
266 	int     dotdot = 0;
267 	Char   *dp, *cwd;
268 	size_t	len;
269 
270 	len = (size_t) (Strlen(dcwd->di_name) + 3);
271 	cwd = xreallocarray(NULL, len, sizeof(Char));
272 	(void) Strlcpy(cwd, dcwd->di_name, len);
273 
274 	/*
275 	 * Ignore . and count ..'s
276 	 */
277 	while (*cp) {
278 	    if (ISDOT(cp)) {
279 		if (*++cp)
280 		    cp++;
281 	    }
282 	    else if (ISDOTDOT(cp)) {
283 		dotdot++;
284 		cp += 2;
285 		if (*cp)
286 		    cp++;
287 	    }
288 	    else
289 		break;
290 	}
291 	while (dotdot > 0)
292 	    if ((dp = Strrchr(cwd, '/'))) {
293 		*dp = '\0';
294 		dotdot--;
295 	    }
296 	    else
297 		break;
298 
299 	if (*cp) {
300 	    cwd[dotdot = Strlen(cwd)] = '/';
301 	    cwd[dotdot + 1] = '\0';
302 	    dp = Strspl(cwd, cp);
303 	    free(cwd);
304 	    return dp;
305 	}
306 	else {
307 	    if (!*cwd) {
308 		cwd[0] = '/';
309 		cwd[1] = '\0';
310 	    }
311 	    return cwd;
312 	}
313     }
314     return Strsave(cp);
315 }
316 
317 /*
318  * dochngd - implement chdir command.
319  */
320 void
321 /*ARGSUSED*/
322 dochngd(Char **v, struct command *t)
323 {
324     Char *cp;
325     struct directory *dp;
326 
327     skipargs(&v, " [<dir>]");
328     printd = 0;
329     if (*v == NULL) {
330 	if ((cp = value(STRhome)) == NULL || *cp == 0)
331 	    stderror(ERR_NAME | ERR_NOHOMEDIR);
332 	if (chdir(short2str(cp)) == -1)
333 	    stderror(ERR_NAME | ERR_CANTCHANGE);
334 	cp = Strsave(cp);
335     }
336     else if (v[1] != NULL) {
337 	stderror(ERR_NAME | ERR_TOOMANY);
338 	/* NOTREACHED */
339 	return;
340     }
341     else if ((dp = dfind(*v)) != 0) {
342 	char   *tmp;
343 
344 	printd = 1;
345 	if (chdir(tmp = short2str(dp->di_name)) == -1)
346 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
347 	dcwd->di_prev->di_next = dcwd->di_next;
348 	dcwd->di_next->di_prev = dcwd->di_prev;
349 	dfree(dcwd);
350 	dnewcwd(dp);
351 	return;
352     }
353     else
354 	cp = dfollow(*v);
355     dp = xcalloc(1, sizeof(struct directory));
356     dp->di_name = cp;
357     dp->di_count = 0;
358     dp->di_next = dcwd->di_next;
359     dp->di_prev = dcwd->di_prev;
360     dp->di_prev->di_next = dp;
361     dp->di_next->di_prev = dp;
362     dfree(dcwd);
363     dnewcwd(dp);
364 }
365 
366 static Char *
367 dgoto(Char *cp)
368 {
369     Char   *dp;
370 
371     if (*cp != '/') {
372 	Char *p, *q;
373 	int     cwdlen;
374 
375 	for (p = dcwd->di_name; *p++;)
376 	    continue;
377 	if ((cwdlen = p - dcwd->di_name - 1) == 1)	/* root */
378 	    cwdlen = 0;
379 	for (p = cp; *p++;)
380 	    continue;
381 	dp = xreallocarray(NULL, (cwdlen + (p - cp) + 1), sizeof(Char));
382 	for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';)
383 	    continue;
384 	if (cwdlen)
385 	    p[-1] = '/';
386 	else
387 	    p--;		/* don't add a / after root */
388 	for (q = cp; (*p++ = *q++) != '\0';)
389 	    continue;
390 	free(cp);
391 	cp = dp;
392 	dp += cwdlen;
393     }
394     else
395 	dp = cp;
396 
397     cp = dcanon(cp, dp);
398     return cp;
399 }
400 
401 /*
402  * dfollow - change to arg directory; fall back on cdpath if not valid
403  */
404 static Char *
405 dfollow(Char *cp)
406 {
407     Char *dp;
408     struct varent *c;
409     char    ebuf[PATH_MAX];
410     int serrno;
411 
412     cp = globone(cp, G_ERROR);
413     /*
414      * if we are ignoring symlinks, try to fix relatives now.
415      */
416     dp = dnormalize(cp);
417     if (chdir(short2str(dp)) >= 0) {
418 	free(cp);
419 	return dgoto(dp);
420     }
421     else {
422 	free(dp);
423 	if (chdir(short2str(cp)) >= 0)
424 	    return dgoto(cp);
425 	serrno = errno;
426     }
427 
428     if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp)
429 	&& (c = adrof(STRcdpath))) {
430 	Char  **cdp;
431 	Char *p;
432 	Char    buf[PATH_MAX];
433 
434 	for (cdp = c->vec; *cdp; cdp++) {
435 	    for (dp = buf, p = *cdp; (*dp++ = *p++) != '\0';)
436 		continue;
437 	    dp[-1] = '/';
438 	    for (p = cp; (*dp++ = *p++) != '\0';)
439 		continue;
440 	    if (chdir(short2str(buf)) >= 0) {
441 		printd = 1;
442 		free(cp);
443 		cp = Strsave(buf);
444 		return dgoto(cp);
445 	    }
446 	}
447     }
448     dp = value(cp);
449     if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) {
450 	free(cp);
451 	cp = Strsave(dp);
452 	printd = 1;
453 	return dgoto(cp);
454     }
455     (void) strlcpy(ebuf, short2str(cp), sizeof ebuf);
456     free(cp);
457     stderror(ERR_SYSTEM, ebuf, strerror(serrno));
458     return (NULL);
459 }
460 
461 
462 /*
463  * dopushd - push new directory onto directory stack.
464  *	with no arguments exchange top and second.
465  *	with numeric argument (+n) bring it to top.
466  */
467 void
468 /*ARGSUSED*/
469 dopushd(Char **v, struct command *t)
470 {
471     struct directory *dp;
472 
473     skipargs(&v, " [<dir>|+<n>]");
474     printd = 1;
475     if (*v == NULL) {
476 	char   *tmp;
477 
478 	if ((dp = dcwd->di_prev) == &dhead)
479 	    dp = dhead.di_prev;
480 	if (dp == dcwd)
481 	    stderror(ERR_NAME | ERR_NODIR);
482 	if (chdir(tmp = short2str(dp->di_name)) == -1)
483 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
484 	dp->di_prev->di_next = dp->di_next;
485 	dp->di_next->di_prev = dp->di_prev;
486 	dp->di_next = dcwd->di_next;
487 	dp->di_prev = dcwd;
488 	dcwd->di_next->di_prev = dp;
489 	dcwd->di_next = dp;
490     }
491     else if (v[1] != NULL) {
492 	stderror(ERR_NAME | ERR_TOOMANY);
493 	/* NOTREACHED */
494 	return;
495     }
496     else if ((dp = dfind(*v)) != NULL) {
497 	char   *tmp;
498 
499 	if (chdir(tmp = short2str(dp->di_name)) == -1)
500 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
501     }
502     else {
503 	Char *ccp;
504 
505 	ccp = dfollow(*v);
506 	dp = xcalloc(1, sizeof(struct directory));
507 	dp->di_name = ccp;
508 	dp->di_count = 0;
509 	dp->di_prev = dcwd;
510 	dp->di_next = dcwd->di_next;
511 	dcwd->di_next = dp;
512 	dp->di_next->di_prev = dp;
513     }
514     dnewcwd(dp);
515 }
516 
517 /*
518  * dfind - find a directory if specified by numeric (+n) argument
519  */
520 static struct directory *
521 dfind(Char *cp)
522 {
523     struct directory *dp;
524     int i;
525     Char *ep;
526 
527     if (*cp++ != '+')
528 	return (0);
529     for (ep = cp; Isdigit(*ep); ep++)
530 	continue;
531     if (*ep)
532 	return (0);
533     i = getn(cp);
534     if (i <= 0)
535 	return (0);
536     for (dp = dcwd; i != 0; i--) {
537 	if ((dp = dp->di_prev) == &dhead)
538 	    dp = dp->di_prev;
539 	if (dp == dcwd)
540 	    stderror(ERR_NAME | ERR_DEEP);
541     }
542     return (dp);
543 }
544 
545 /*
546  * dopopd - pop a directory out of the directory stack
547  *	with a numeric argument just discard it.
548  */
549 void
550 /*ARGSUSED*/
551 dopopd(Char **v, struct command *t)
552 {
553     struct directory *dp, *p = NULL;
554 
555     skipargs(&v, " [+<n>]");
556     printd = 1;
557     if (*v == NULL)
558 	dp = dcwd;
559     else if (v[1] != NULL) {
560 	stderror(ERR_NAME | ERR_TOOMANY);
561 	/* NOTREACHED */
562 	return;
563     }
564     else if ((dp = dfind(*v)) == 0)
565 	stderror(ERR_NAME | ERR_BADDIR);
566     if (dp->di_prev == &dhead && dp->di_next == &dhead)
567 	stderror(ERR_NAME | ERR_EMPTY);
568     if (dp == dcwd) {
569 	char   *tmp;
570 
571 	if ((p = dp->di_prev) == &dhead)
572 	    p = dhead.di_prev;
573 	if (chdir(tmp = short2str(p->di_name)) == -1)
574 	    stderror(ERR_SYSTEM, tmp, strerror(errno));
575     }
576     dp->di_prev->di_next = dp->di_next;
577     dp->di_next->di_prev = dp->di_prev;
578     if (dp == dcwd)
579 	dnewcwd(p);
580     else {
581 	printdirs();
582     }
583     dfree(dp);
584 }
585 
586 /*
587  * dfree - free the directory (or keep it if it still has ref count)
588  */
589 void
590 dfree(struct directory *dp)
591 {
592 
593     if (dp->di_count != 0) {
594 	dp->di_next = dp->di_prev = 0;
595     }
596     else {
597 	free((char *) dp->di_name);
598 	free(dp);
599     }
600 }
601 
602 /*
603  * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
604  *	we are of course assuming that the file system is standardly
605  *	constructed (always have ..'s, directories have links)
606  */
607 Char   *
608 dcanon(Char *cp, Char *p)
609 {
610     Char *sp;
611     Char *p1, *p2;	/* general purpose */
612     bool    slash;
613 
614     Char    link[PATH_MAX];
615     char    tlink[PATH_MAX];
616     int     cc;
617     Char   *newcp;
618 
619     /*
620      * christos: if the path given does not start with a slash prepend cwd. If
621      * cwd does not start with a path or the result would be too long abort().
622      */
623     if (*cp != '/') {
624 	Char    tmpdir[PATH_MAX];
625 
626 	p1 = value(STRcwd);
627 	if (p1 == NULL || *p1 != '/')
628 	    abort();
629 	if (Strlen(p1) + Strlen(cp) + 1 >= PATH_MAX)
630 	    abort();
631 	(void) Strlcpy(tmpdir, p1, sizeof tmpdir/sizeof(Char));
632 	(void) Strlcat(tmpdir, STRslash, sizeof tmpdir/sizeof(Char));
633 	(void) Strlcat(tmpdir, cp, sizeof tmpdir/sizeof(Char));
634 	free(cp);
635 	cp = p = Strsave(tmpdir);
636     }
637 
638     while (*p) {		/* for each component */
639 	sp = p;			/* save slash address */
640 	while (*++p == '/')	/* flush extra slashes */
641 	    continue;
642 	if (p != ++sp)
643 	    for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';)
644 		continue;
645 	p = sp;			/* save start of component */
646 	slash = 0;
647 	while (*p)		/* find next slash or end of path */
648 	    if (*++p == '/') {
649 		slash = 1;
650 		*p = 0;
651 		break;
652 	    }
653 
654 	if (*sp == '\0')	/* if component is null */
655 	    if (--sp == cp)	/* if path is one char (i.e. /) */
656 		break;
657 	    else
658 		*sp = '\0';
659 	else if (sp[0] == '.' && sp[1] == 0) {
660 	    if (slash) {
661 		for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';)
662 		    continue;
663 		p = --sp;
664 	    }
665 	    else if (--sp != cp)
666 		*sp = '\0';
667 	}
668 	else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) {
669 	    /*
670 	     * We have something like "yyy/xxx/..", where "yyy" can be null or
671 	     * a path starting at /, and "xxx" is a single component. Before
672 	     * compressing "xxx/..", we want to expand "yyy/xxx", if it is a
673 	     * symbolic link.
674 	     */
675 	    *--sp = 0;		/* form the pathname for readlink */
676 	    if (sp != cp && !adrof(STRignore_symlinks) &&
677 		(cc = readlink(short2str(cp), tlink,
678 			       sizeof tlink-1)) >= 0) {
679 		tlink[cc] = '\0';
680 		(void) Strlcpy(link, str2short(tlink), sizeof link/sizeof(Char));
681 
682 		if (slash)
683 		    *p = '/';
684 		/*
685 		 * Point p to the '/' in "/..", and restore the '/'.
686 		 */
687 		*(p = sp) = '/';
688 		/*
689 		 * find length of p
690 		 */
691 		for (p1 = p; *p1++;)
692 		    continue;
693 		if (*link != '/') {
694 		    /*
695 		     * Relative path, expand it between the "yyy/" and the
696 		     * "/..". First, back sp up to the character past "yyy/".
697 		     */
698 		    while (*--sp != '/')
699 			continue;
700 		    sp++;
701 		    *sp = 0;
702 		    /*
703 		     * New length is "yyy/" + link + "/.." and rest
704 		     */
705 		    p1 = newcp = xreallocarray(NULL,
706 			(sp - cp) + cc + (p1 - p), sizeof(Char));
707 		    /*
708 		     * Copy new path into newcp
709 		     */
710 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
711 			continue;
712 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
713 			continue;
714 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
715 			continue;
716 		    /*
717 		     * Restart canonicalization at expanded "/xxx".
718 		     */
719 		    p = sp - cp - 1 + newcp;
720 		}
721 		else {
722 		    /*
723 		     * New length is link + "/.." and rest
724 		     */
725 		    p1 = newcp = xreallocarray(NULL, cc + (p1 - p),
726 		        sizeof(Char));
727 		    /*
728 		     * Copy new path into newcp
729 		     */
730 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
731 			continue;
732 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
733 			continue;
734 		    /*
735 		     * Restart canonicalization at beginning
736 		     */
737 		    p = newcp;
738 		}
739 		free(cp);
740 		cp = newcp;
741 		continue;	/* canonicalize the link */
742 	    }
743 	    *sp = '/';
744 	    if (sp != cp)
745 		while (*--sp != '/')
746 		    continue;
747 	    if (slash) {
748 		for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';)
749 		    continue;
750 		p = sp;
751 	    }
752 	    else if (cp == sp)
753 		*++sp = '\0';
754 	    else
755 		*sp = '\0';
756 	}
757 	else {			/* normal dir name (not . or .. or nothing) */
758 
759 	    if (sp != cp && adrof(STRchase_symlinks) &&
760 		!adrof(STRignore_symlinks) &&
761 		(cc = readlink(short2str(cp), tlink,
762 			       sizeof tlink-1)) >= 0) {
763 		tlink[cc] = '\0';
764 		(void) Strlcpy(link, str2short(tlink), sizeof link/sizeof(Char));
765 
766 		/*
767 		 * restore the '/'.
768 		 */
769 		if (slash)
770 		    *p = '/';
771 
772 		/*
773 		 * point sp to p (rather than backing up).
774 		 */
775 		sp = p;
776 
777 		/*
778 		 * find length of p
779 		 */
780 		for (p1 = p; *p1++;)
781 		    continue;
782 		if (*link != '/') {
783 		    /*
784 		     * Relative path, expand it between the "yyy/" and the
785 		     * remainder. First, back sp up to the character past
786 		     * "yyy/".
787 		     */
788 		    while (*--sp != '/')
789 			continue;
790 		    sp++;
791 		    *sp = 0;
792 		    /*
793 		     * New length is "yyy/" + link + "/.." and rest
794 		     */
795 		    p1 = newcp = xreallocarray(NULL,
796 			  (sp - cp) + cc + (p1 - p), sizeof(Char));
797 		    /*
798 		     * Copy new path into newcp
799 		     */
800 		    for (p2 = cp; (*p1++ = *p2++) != '\0';)
801 			continue;
802 		    for (p1--, p2 = link; (*p1++ = *p2++) != '\0';)
803 			continue;
804 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
805 			continue;
806 		    /*
807 		     * Restart canonicalization at expanded "/xxx".
808 		     */
809 		    p = sp - cp - 1 + newcp;
810 		}
811 		else {
812 		    /*
813 		     * New length is link + the rest
814 		     */
815 		    p1 = newcp = xreallocarray(NULL, cc + (p1 - p), sizeof(Char));
816 		    /*
817 		     * Copy new path into newcp
818 		     */
819 		    for (p2 = link; (*p1++ = *p2++) != '\0';)
820 			continue;
821 		    for (p1--, p2 = p; (*p1++ = *p2++) != '\0';)
822 			continue;
823 		    /*
824 		     * Restart canonicalization at beginning
825 		     */
826 		    p = newcp;
827 		}
828 		free(cp);
829 		cp = newcp;
830 		continue;	/* canonicalize the link */
831 	    }
832 	    if (slash)
833 		*p = '/';
834 	}
835     }
836 
837     /*
838      * fix home...
839      */
840     p1 = value(STRhome);
841     cc = Strlen(p1);
842     /*
843      * See if we're not in a subdir of STRhome
844      */
845     if (p1 && *p1 == '/' &&
846 	(Strncmp(p1, cp, cc) != 0 || (cp[cc] != '/' && cp[cc] != '\0'))) {
847 	static ino_t home_ino = -1;
848 	static dev_t home_dev = -1;
849 	static Char *home_ptr = NULL;
850 	struct stat statbuf;
851 
852 	/*
853 	 * Get dev and ino of STRhome
854 	 */
855 	if (home_ptr != p1 &&
856 	    stat(short2str(p1), &statbuf) != -1) {
857 	    home_dev = statbuf.st_dev;
858 	    home_ino = statbuf.st_ino;
859 	    home_ptr = p1;
860 	}
861 	/*
862 	 * Start comparing dev & ino backwards
863 	 */
864 	Strlcpy(link, cp, sizeof link/sizeof(Char));
865 	p2 = link;
866 	for (sp = NULL; *p2 && stat(short2str(p2), &statbuf) != -1;) {
867 	    if (statbuf.st_dev == home_dev &&
868 		statbuf.st_ino == home_ino) {
869 		sp = (Char *) - 1;
870 		break;
871 	    }
872 	    if ((sp = Strrchr(p2, '/')) != NULL)
873 		*sp = '\0';
874 	}
875 	/*
876 	 * See if we found it
877 	 */
878 	if (*p2 && sp == (Char *) -1) {
879 	    /*
880 	     * Use STRhome to make '~' work
881 	     */
882 	    newcp = Strspl(p1, cp + Strlen(p2));
883 	    free(cp);
884 	    cp = newcp;
885 	}
886     }
887     return cp;
888 }
889 
890 
891 /*
892  * dnewcwd - make a new directory in the loop the current one
893  */
894 static void
895 dnewcwd(struct directory *dp)
896 {
897     dcwd = dp;
898     dset(dcwd->di_name);
899     if (printd && !(adrof(STRpushdsilent)))
900 	printdirs();
901 }
902