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