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