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